diff --git a/assets/readme/examples/business-report.pptx b/assets/readme/examples/business-report.pptx new file mode 100644 index 00000000..7537512b Binary files /dev/null and b/assets/readme/examples/business-report.pptx differ diff --git a/assets/readme/examples/financial-report.pptx b/assets/readme/examples/financial-report.pptx new file mode 100644 index 00000000..3c8c4cf0 Binary files /dev/null and b/assets/readme/examples/financial-report.pptx differ diff --git a/assets/readme/examples/master-showcase.pptx b/assets/readme/examples/master-showcase.pptx new file mode 100644 index 00000000..6d78b8bd Binary files /dev/null and b/assets/readme/examples/master-showcase.pptx differ diff --git a/examples/README.md b/examples/README.md index 89f87e4b..1b4f20f0 100644 --- a/examples/README.md +++ b/examples/README.md @@ -119,9 +119,9 @@ are with the canonical DSL, then jump to its detailed section below. | [Word export (DOCX)](#word-export-docx) | `DocxSemanticBackend` — the same session renders a fixed-layout PDF and an editable Word file; paragraphs / lists / tables / images map 1:1, charts fall back to their data table | [PDF](../assets/readme/examples/word-export-companion.pdf) · [DOCX](../assets/readme/examples/word-export-companion.docx) · [Source](src/main/java/com/demcha/examples/features/docx/WordExportExample.java) | | [Layout snapshot regression](#layout-snapshot-regression) | Deterministic `layoutSnapshot()` workflow with baseline + drift report — production regression-testing pattern | [PDF](../assets/readme/examples/invoice-snapshot-regression.pdf) · [Source](src/main/java/com/demcha/examples/features/snapshots/LayoutSnapshotRegressionExample.java) | | [Debug overlay](#debug-overlay) | `DocumentDebugOptions` — guide lines + semantic node-path labels on the sheet; trace any misplaced block back to the builder call that authored it | [PDF](../assets/readme/examples/debug-overlay.pdf) · [Source](src/main/java/com/demcha/examples/features/debug/DebugOverlayExample.java) | -| [Business report cover](#business-report-cover) | Single-page Q1 investor brief — hero image, KPI cards, bar chart, metrics table | [PDF](../assets/readme/examples/business-report.pdf) · [Source](src/main/java/com/demcha/examples/flagships/BusinessReportExample.java) | -| Financial report one-pager | Single-page monthly financial dashboard — three margin gauges, cash & stacked-OPEX charts, a revenue donut, and forecast bars; all v1.8 native vector charts plus inline sparklines and a path-clipped photo masthead | [PDF](../assets/readme/examples/financial-report.pdf) · [Source](src/main/java/com/demcha/examples/flagships/FinancialReportExample.java) | -| [Master showcase](#master-showcase) | Kitchen-sink "Q2 sample report" combining the canonical surface end-to-end | [PDF](../assets/readme/examples/master-showcase.pdf) · [Source](src/main/java/com/demcha/examples/flagships/MasterShowcaseExample.java) | +| [Business report cover](#business-report-cover) | Single-page Q1 investor brief — hero image, KPI cards, bar chart, metrics table; the same composition also renders as an editable PowerPoint deck (KPI cards, chart, and table stay native shapes) via `buildPptx()` | [PDF](../assets/readme/examples/business-report.pdf) · [PPTX](../assets/readme/examples/business-report.pptx) · [Source](src/main/java/com/demcha/examples/flagships/BusinessReportExample.java) · [PPTX source](src/main/java/com/demcha/examples/flagships/BusinessReportPptxExample.java) | +| Financial report one-pager | Single-page monthly financial dashboard — three margin gauges, cash & stacked-OPEX charts, a revenue donut, and forecast bars; all v1.8 native vector charts plus inline sparklines and a path-clipped photo masthead; the same composition also renders as an editable PowerPoint deck via `buildPptx()` | [PDF](../assets/readme/examples/financial-report.pdf) · [PPTX](../assets/readme/examples/financial-report.pptx) · [Source](src/main/java/com/demcha/examples/flagships/FinancialReportExample.java) · [PPTX source](src/main/java/com/demcha/examples/flagships/FinancialReportPptxExample.java) | +| [Master showcase](#master-showcase) | Kitchen-sink "Q2 sample report" combining the canonical surface end-to-end; the same composition also renders as a multi-slide editable PowerPoint deck via `buildPptx()` | [PDF](../assets/readme/examples/master-showcase.pdf) · [PPTX](../assets/readme/examples/master-showcase.pptx) · [Source](src/main/java/com/demcha/examples/flagships/MasterShowcaseExample.java) · [PPTX source](src/main/java/com/demcha/examples/flagships/MasterShowcasePptxExample.java) | | Feature catalog | Browsable reference PDF: every shipped capability as a block — outline-clickable heading, the exact API call, the rendered result right under it | [PDF](../assets/readme/examples/feature-catalog.pdf) · [Source](src/main/java/com/demcha/examples/flagships/FeatureCatalogExample.java) | | Book template | A full novel front: full-bleed wave cover, a clickable dotted-leader table of contents with live page numbers, and chapters — the v1.9 book primitives (`pageMargins`, `addTableOfContents`, `DocumentPageNumbering`, container `bookmark`, `viewerPreferences`) in **one session**, no external PDF merge | [PDF](../assets/readme/examples/book-template.pdf) · [Source](src/main/java/com/demcha/examples/features/title/BookTemplateExample.java) | @@ -1104,8 +1104,12 @@ 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. +The same composition also emits an editable PowerPoint deck (`BusinessReportPptxExample` — one page, one slide; KPI cards, the native chart, and the metrics table stay editable shapes). + [📄 View PDF](../assets/readme/examples/business-report.pdf) · -[📜 Full source](src/main/java/com/demcha/examples/flagships/BusinessReportExample.java) +[🖥 View PPTX](../assets/readme/examples/business-report.pptx) · +[📜 Full source](src/main/java/com/demcha/examples/flagships/BusinessReportExample.java) · +[📜 PPTX twin](src/main/java/com/demcha/examples/flagships/BusinessReportPptxExample.java) ### Master showcase @@ -1115,8 +1119,12 @@ 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. +The same composition also emits an editable PowerPoint deck (`MasterShowcasePptxExample` — each page becomes one slide; rich text, the advanced table, and chrome stay native shapes, and only the rotated clip-masked seal and the barcodes rasterise). + [📄 View PDF](../assets/readme/examples/master-showcase.pdf) · -[📜 Full source](src/main/java/com/demcha/examples/flagships/MasterShowcaseExample.java) +[🖥 View PPTX](../assets/readme/examples/master-showcase.pptx) · +[📜 Full source](src/main/java/com/demcha/examples/flagships/MasterShowcaseExample.java) · +[📜 PPTX twin](src/main/java/com/demcha/examples/flagships/MasterShowcasePptxExample.java) --- diff --git a/examples/src/main/java/com/demcha/examples/GenerateAllExamples.java b/examples/src/main/java/com/demcha/examples/GenerateAllExamples.java index 82499ad1..77bfb116 100644 --- a/examples/src/main/java/com/demcha/examples/GenerateAllExamples.java +++ b/examples/src/main/java/com/demcha/examples/GenerateAllExamples.java @@ -44,11 +44,14 @@ import com.demcha.examples.features.title.PoetryTitlePageExample; import com.demcha.examples.features.transforms.TransformsExample; import com.demcha.examples.flagships.BusinessReportExample; +import com.demcha.examples.flagships.BusinessReportPptxExample; 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.FinancialReportPptxExample; import com.demcha.examples.flagships.MasterShowcaseExample; +import com.demcha.examples.flagships.MasterShowcasePptxExample; import com.demcha.examples.flagships.MavenBannerPptxExample; import com.demcha.examples.flagships.ModuleFirstFileExample; import com.demcha.examples.flagships.TwinOutputExample; @@ -212,13 +215,16 @@ public static void main(String[] args) throws Exception { // === Flagships === System.out.println("Generated: " + ModuleFirstFileExample.generate()); System.out.println("Generated: " + MasterShowcaseExample.generate()); + System.out.println("Generated: " + MasterShowcasePptxExample.generate()); System.out.println("Generated: " + FeatureCatalogExample.generate()); System.out.println("Generated: " + BusinessReportExample.generate()); + System.out.println("Generated: " + BusinessReportPptxExample.generate()); System.out.println("Generated: " + EngineDeckExample.generate()); System.out.println("Generated: " + EngineDeckPptxExample.generate()); System.out.println("Generated: " + MavenBannerPptxExample.generate()); System.out.println("Generated: " + TwinOutputExample.generate()); System.out.println("Generated: " + FinancialReportExample.generate()); + System.out.println("Generated: " + FinancialReportPptxExample.generate()); System.out.println("Generated: " + BookTemplateExample.generate()); System.out.println("Generated: " + PoetryTitlePageExample.generate()); } diff --git a/examples/src/main/java/com/demcha/examples/flagships/BusinessReportExample.java b/examples/src/main/java/com/demcha/examples/flagships/BusinessReportExample.java index 1f74967a..f801d8d1 100644 --- a/examples/src/main/java/com/demcha/examples/flagships/BusinessReportExample.java +++ b/examples/src/main/java/com/demcha/examples/flagships/BusinessReportExample.java @@ -73,180 +73,199 @@ private BusinessReportExample() { public static Path generate() throws Exception { Path outputFile = ExampleOutputPaths.prepare("flagships", "business-report.pdf"); - try (DocumentSession document = GraphCompose.document(outputFile) - .pageSize(DocumentPageSize.A4) - .pageBackground(PAPER) - .margin(28, 40, 28, 40) - .create()) { - - // Running footer — repeats on every page with live page numbers, - // instead of a hardcoded "Page 1 of 8" paragraph in the flow. - document.footer(com.demcha.compose.document.output.DocumentHeaderFooter.builder() - .zone(com.demcha.compose.document.output.DocumentHeaderFooterZone.FOOTER) - .leftText("Confidential and proprietary") - .rightText("Page {page} of {pages}") - .fontSize(8f) - .textColor(MUTED) - .showSeparator(true) - .separatorColor(SUBTLE_RULE) - .separatorThickness(0.5f) - .build()); - - document.pageFlow() - .name("BusinessReportCover") - .spacing(11) - - // Top band — report identifier + month - .addRow("Band", row -> row - .spacing(0) - .weights(1, 1) - .addSection("BandLeft", section -> section - .addParagraph(p -> p - .text("Q1 2024 - BUSINESS REPORT") - .textStyle(bandLeft()) - .margin(DocumentInsets.zero()))) - .addSection("BandRight", section -> section - .addParagraph(p -> p - .text("APRIL 2024") - .textStyle(bandRight()) - .align(TextAlign.RIGHT) - .margin(DocumentInsets.zero())))) - - // Thin gold rule under the band - rendered as a thin - // shape because LineBuilder applies thickness-based - // padding that pushes the natural width past the - // page inner width. - .addShape(s -> s.size(500, 0.8).fillColor(GOLD).margin(DocumentInsets.zero())) - - // Hero — headline + image - .addRow("Hero", row -> row - .spacing(18) - .weights(11, 9) - .addSection("HeroCopy", section -> section - .padding(new DocumentInsets(4, 0, 0, 0)) - .spacing(4) - .addParagraph(p -> p - .text("Building the future") - .textStyle(heroTitle()) - .margin(DocumentInsets.zero())) - .addParagraph(p -> p - .text("with clarity and purpose") - .textStyle(heroTitle()) - .margin(DocumentInsets.zero())) - .addParagraph(p -> p - .text("We combine strategy, design, and engineering to deliver impactful digital products that drive real business results and lasting customer value.") - .textStyle(heroBody()) - .lineSpacing(1.4) - .margin(new DocumentInsets(4, 0, 0, 0)))) - .addSection("HeroImage", section -> section - .padding(DocumentInsets.zero()) - // The hero scene is fully vector now: a - // gradient-sky shape with two polygon - // mountain ranges, clipped to the rounded - // frame. No raster, no AWT. - .addContainer(frame -> frame - .name("HeroFrame") - .roundedRect(210, 110, 12) - .fillColor(NAVY_DARK) - .stroke(DocumentStroke.of(GOLD, 1.5)) - .clipPolicy(ClipPolicy.CLIP_PATH) - .center(buildHeroScene(212, 112))))) - - // Three KPI cards - .addRow("KpiRow", row -> row - .spacing(14) - .weights(1, 1, 1) - .addSection("Kpi1", section -> kpiCard(section, - "$", "24%", "Revenue Growth", - "Strong performance across all core business units.")) - .addSection("Kpi2", section -> kpiCard(section, - "U", "18k+", "Active Customers", - "Growing community and engagement.")) - .addSection("Kpi3", section -> kpiCard(section, - "%", "98.6%", "SLA Compliance", - "Reliable platform with enterprise-grade uptime."))) - - // Highlights + Performance overview row - .addRow("InsightsRow", row -> row - .spacing(18) - .weights(8, 12) - .addSection("Highlights", section -> section - .softPanel(DocumentColor.WHITE, DocumentCornerRadius.right(10), 16) - .stroke(DocumentStroke.of(CARD_RING, 0.5)) - .accentLeft(NAVY, 3) - .spacing(8) - .addParagraph(p -> p - .text("Strategic highlights") - .textStyle(sectionTitle()) - .margin(DocumentInsets.zero())) - .addParagraph(p -> p - .text("• Expanded market presence in key regions with double-digit growth.") - .textStyle(highlightLine()) - .lineSpacing(1.45) - .margin(new DocumentInsets(2, 0, 0, 0))) - .addParagraph(p -> p - .text("• Launched new platform capabilities improving performance and scalability.") - .textStyle(highlightLine()) - .lineSpacing(1.45) - .margin(DocumentInsets.zero())) - .addParagraph(p -> p - .text("• Strengthened partnerships to accelerate innovation and delivery.") - .textStyle(highlightLine()) - .lineSpacing(1.45) - .margin(DocumentInsets.zero()))) - .addSection("Chart", section -> section - .softPanel(DocumentColor.WHITE, DocumentCornerRadius.right(10), 14) - .stroke(DocumentStroke.of(CARD_RING, 0.5)) - .accentLeft(GOLD, 3) - .spacing(6) - .addParagraph(p -> p - .text("Performance overview") - .textStyle(sectionTitle()) - .margin(DocumentInsets.zero())) - .addRich(rich -> rich - .style("Revenue", DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) - .size(9) - .color(NAVY) - .build()) - .style(" ", legendLabel()) - .style("Profit", DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) - .size(9) - .color(GOLD) - .build())) - .add(buildChart()))) - - // Key metrics — wrapped in a soft panel with an - // accent strip so the table reads as a distinct - // dashboard card rather than a bare grid. - .addSection("MetricsCard", card -> card - .softPanel(DocumentColor.WHITE, DocumentCornerRadius.bottom(10), 12) - .stroke(DocumentStroke.of(CARD_RING, 0.5)) - .accentTop(GOLD, 1.5) - .spacing(4) - .addParagraph(p -> p - .text("Key metrics") - .textStyle(sectionTitle()) - .margin(DocumentInsets.zero())) - .addParagraph(p -> p - .text("Year-over-year delta in the right column. Retention reported in percentage points.") - .textStyle(DocumentTextStyle.builder() - .fontName(FontName.HELVETICA) - .size(8.4) - .color(MUTED) - .build()) - .lineSpacing(1.35) - .margin(new DocumentInsets(0, 0, 2, 0))) - .addTable(BusinessReportExample::buildMetricsTable)) - .build(); - + try (DocumentSession document = document(outputFile).create()) { + compose(document); document.buildPdf(); } - return outputFile; } + /** + * The example's canonical session setup, shared by the PDF {@link #generate()} + * and its PPTX twin so both emit from an identically configured document — + * the "one source, two formats" contract. + * + * @param outputFile the file the session writes to (.pdf or .pptx) + * @return the configured builder, ready to {@code create()} + */ + static GraphCompose.DocumentBuilder document(Path outputFile) { + return GraphCompose.document(outputFile) + .pageSize(DocumentPageSize.A4) + .pageBackground(PAPER) + .margin(28, 40, 28, 40); + } + + /** + * Composes the single-page investor report into {@code document}. Shared by + * {@link #generate()} and its PPTX twin so both lay out identical geometry. + * + * @param document the open session to compose into + */ + static void compose(DocumentSession document) { + + // Running footer — repeats on every page with live page numbers, + // instead of a hardcoded "Page 1 of 8" paragraph in the flow. + document.footer(com.demcha.compose.document.output.DocumentHeaderFooter.builder() + .zone(com.demcha.compose.document.output.DocumentHeaderFooterZone.FOOTER) + .leftText("Confidential and proprietary") + .rightText("Page {page} of {pages}") + .fontSize(8f) + .textColor(MUTED) + .showSeparator(true) + .separatorColor(SUBTLE_RULE) + .separatorThickness(0.5f) + .build()); + + document.pageFlow() + .name("BusinessReportCover") + .spacing(11) + + // Top band — report identifier + month + .addRow("Band", row -> row + .spacing(0) + .weights(1, 1) + .addSection("BandLeft", section -> section + .addParagraph(p -> p + .text("Q1 2024 - BUSINESS REPORT") + .textStyle(bandLeft()) + .margin(DocumentInsets.zero()))) + .addSection("BandRight", section -> section + .addParagraph(p -> p + .text("APRIL 2024") + .textStyle(bandRight()) + .align(TextAlign.RIGHT) + .margin(DocumentInsets.zero())))) + + // Thin gold rule under the band - rendered as a thin + // shape because LineBuilder applies thickness-based + // padding that pushes the natural width past the + // page inner width. + .addShape(s -> s.size(500, 0.8).fillColor(GOLD).margin(DocumentInsets.zero())) + + // Hero — headline + image + .addRow("Hero", row -> row + .spacing(18) + .weights(11, 9) + .addSection("HeroCopy", section -> section + .padding(new DocumentInsets(4, 0, 0, 0)) + .spacing(4) + .addParagraph(p -> p + .text("Building the future") + .textStyle(heroTitle()) + .margin(DocumentInsets.zero())) + .addParagraph(p -> p + .text("with clarity and purpose") + .textStyle(heroTitle()) + .margin(DocumentInsets.zero())) + .addParagraph(p -> p + .text("We combine strategy, design, and engineering to deliver impactful digital products that drive real business results and lasting customer value.") + .textStyle(heroBody()) + .lineSpacing(1.4) + .margin(new DocumentInsets(4, 0, 0, 0)))) + .addSection("HeroImage", section -> section + .padding(DocumentInsets.zero()) + // The hero scene is fully vector now: a + // gradient-sky shape with two polygon + // mountain ranges, clipped to the rounded + // frame. No raster, no AWT. + .addContainer(frame -> frame + .name("HeroFrame") + .roundedRect(210, 110, 12) + .fillColor(NAVY_DARK) + .stroke(DocumentStroke.of(GOLD, 1.5)) + .clipPolicy(ClipPolicy.CLIP_PATH) + .center(buildHeroScene(212, 112))))) + + // Three KPI cards + .addRow("KpiRow", row -> row + .spacing(14) + .weights(1, 1, 1) + .addSection("Kpi1", section -> kpiCard(section, + "$", "24%", "Revenue Growth", + "Strong performance across all core business units.")) + .addSection("Kpi2", section -> kpiCard(section, + "U", "18k+", "Active Customers", + "Growing community and engagement.")) + .addSection("Kpi3", section -> kpiCard(section, + "%", "98.6%", "SLA Compliance", + "Reliable platform with enterprise-grade uptime."))) + + // Highlights + Performance overview row + .addRow("InsightsRow", row -> row + .spacing(18) + .weights(8, 12) + .addSection("Highlights", section -> section + .softPanel(DocumentColor.WHITE, DocumentCornerRadius.right(10), 16) + .stroke(DocumentStroke.of(CARD_RING, 0.5)) + .accentLeft(NAVY, 3) + .spacing(8) + .addParagraph(p -> p + .text("Strategic highlights") + .textStyle(sectionTitle()) + .margin(DocumentInsets.zero())) + .addParagraph(p -> p + .text("• Expanded market presence in key regions with double-digit growth.") + .textStyle(highlightLine()) + .lineSpacing(1.45) + .margin(new DocumentInsets(2, 0, 0, 0))) + .addParagraph(p -> p + .text("• Launched new platform capabilities improving performance and scalability.") + .textStyle(highlightLine()) + .lineSpacing(1.45) + .margin(DocumentInsets.zero())) + .addParagraph(p -> p + .text("• Strengthened partnerships to accelerate innovation and delivery.") + .textStyle(highlightLine()) + .lineSpacing(1.45) + .margin(DocumentInsets.zero()))) + .addSection("Chart", section -> section + .softPanel(DocumentColor.WHITE, DocumentCornerRadius.right(10), 14) + .stroke(DocumentStroke.of(CARD_RING, 0.5)) + .accentLeft(GOLD, 3) + .spacing(6) + .addParagraph(p -> p + .text("Performance overview") + .textStyle(sectionTitle()) + .margin(DocumentInsets.zero())) + .addRich(rich -> rich + .style("Revenue", DocumentTextStyle.builder() + .fontName(FontName.HELVETICA_BOLD) + .size(9) + .color(NAVY) + .build()) + .style(" ", legendLabel()) + .style("Profit", DocumentTextStyle.builder() + .fontName(FontName.HELVETICA_BOLD) + .size(9) + .color(GOLD) + .build())) + .add(buildChart()))) + + // Key metrics — wrapped in a soft panel with an + // accent strip so the table reads as a distinct + // dashboard card rather than a bare grid. + .addSection("MetricsCard", card -> card + .softPanel(DocumentColor.WHITE, DocumentCornerRadius.bottom(10), 12) + .stroke(DocumentStroke.of(CARD_RING, 0.5)) + .accentTop(GOLD, 1.5) + .spacing(4) + .addParagraph(p -> p + .text("Key metrics") + .textStyle(sectionTitle()) + .margin(DocumentInsets.zero())) + .addParagraph(p -> p + .text("Year-over-year delta in the right column. Retention reported in percentage points.") + .textStyle(DocumentTextStyle.builder() + .fontName(FontName.HELVETICA) + .size(8.4) + .color(MUTED) + .build()) + .lineSpacing(1.35) + .margin(new DocumentInsets(0, 0, 2, 0))) + .addTable(BusinessReportExample::buildMetricsTable)) + .build(); + } + public static void main(String[] args) throws Exception { System.out.println("Generated: " + generate()); } diff --git a/examples/src/main/java/com/demcha/examples/flagships/BusinessReportPptxExample.java b/examples/src/main/java/com/demcha/examples/flagships/BusinessReportPptxExample.java new file mode 100644 index 00000000..6fc22b9a --- /dev/null +++ b/examples/src/main/java/com/demcha/examples/flagships/BusinessReportPptxExample.java @@ -0,0 +1,48 @@ +package com.demcha.examples.flagships; + +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.examples.support.ExampleOutputPaths; + +import java.nio.file.Path; + +/** + * The {@link BusinessReportExample} investor report emitted as a PowerPoint + * deck: the same composition, built once through the shared session config and + * written through the fixed-layout PPTX backend via + * {@link DocumentSession#buildPptx(java.nio.file.Path)}. One resolved page becomes one + * identically-sized slide, so the report opens in PowerPoint as an editable copy + * of the PDF — the KPI cards, the native revenue/profit chart, and the metrics + * table arrive as editable shapes and text frames, not a screenshot. + * + * @since 2.1.0 + */ +public final class BusinessReportPptxExample { + + private BusinessReportPptxExample() { + } + + /** + * Builds the investor report as the .pptx twin of {@link BusinessReportExample}. + * + * @return the generated file path + * @throws Exception when composition or rendering fails + */ + public static Path generate() throws Exception { + Path outputFile = ExampleOutputPaths.prepare("flagships", "business-report.pptx"); + try (DocumentSession document = BusinessReportExample.document(outputFile).create()) { + BusinessReportExample.compose(document); + document.buildPptx(outputFile); + } + 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()); + } +} diff --git a/examples/src/main/java/com/demcha/examples/flagships/FinancialReportExample.java b/examples/src/main/java/com/demcha/examples/flagships/FinancialReportExample.java index 6b1961ee..f2a0b45e 100644 --- a/examples/src/main/java/com/demcha/examples/flagships/FinancialReportExample.java +++ b/examples/src/main/java/com/demcha/examples/flagships/FinancialReportExample.java @@ -99,17 +99,28 @@ private FinancialReportExample() { */ public static Path generate() throws Exception { Path outputFile = ExampleOutputPaths.prepare("flagships", "financial-report.pdf"); - try (DocumentSession document = GraphCompose.document(outputFile) - .pageSize(DocumentPageSize.A4) - .pageBackground(PAPER) - .margin(18, 28, 14, 28) - .create()) { + try (DocumentSession document = document(outputFile).create()) { compose(document); document.buildPdf(); } return outputFile; } + /** + * The example's canonical session setup, shared by the PDF {@link #generate()} + * and its PPTX twin so both emit from an identically configured document — + * the "one source, two formats" contract. + * + * @param outputFile the file the session writes to (.pdf or .pptx) + * @return the configured builder, ready to {@code create()} + */ + static GraphCompose.DocumentBuilder document(Path outputFile) { + return GraphCompose.document(outputFile) + .pageSize(DocumentPageSize.A4) + .pageBackground(PAPER) + .margin(18, 28, 14, 28); + } + /** * Composes the one-pager into {@code document}. Shared by {@link #generate()} * and the layout snapshot test, so both lay out identical geometry — the test diff --git a/examples/src/main/java/com/demcha/examples/flagships/FinancialReportPptxExample.java b/examples/src/main/java/com/demcha/examples/flagships/FinancialReportPptxExample.java new file mode 100644 index 00000000..713859f6 --- /dev/null +++ b/examples/src/main/java/com/demcha/examples/flagships/FinancialReportPptxExample.java @@ -0,0 +1,48 @@ +package com.demcha.examples.flagships; + +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.examples.support.ExampleOutputPaths; + +import java.nio.file.Path; + +/** + * The {@link FinancialReportExample} one-page dashboard emitted as a PowerPoint + * deck: the same composition, built once through the shared session config and + * written through the fixed-layout PPTX backend via + * {@link DocumentSession#buildPptx(java.nio.file.Path)}. One resolved page becomes one + * identically-sized slide, so the dashboard opens in PowerPoint as an editable + * copy of the PDF — metric tiles, the native charts, and the data table stay + * editable shapes and text frames; the masthead photo travels as a picture. + * + * @since 2.1.0 + */ +public final class FinancialReportPptxExample { + + private FinancialReportPptxExample() { + } + + /** + * Builds the dashboard as the .pptx twin of {@link FinancialReportExample}. + * + * @return the generated file path + * @throws Exception when composition or rendering fails + */ + public static Path generate() throws Exception { + Path outputFile = ExampleOutputPaths.prepare("flagships", "financial-report.pptx"); + try (DocumentSession document = FinancialReportExample.document(outputFile).create()) { + FinancialReportExample.compose(document); + document.buildPptx(outputFile); + } + 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()); + } +} diff --git a/examples/src/main/java/com/demcha/examples/flagships/MasterShowcaseExample.java b/examples/src/main/java/com/demcha/examples/flagships/MasterShowcaseExample.java index 61c59cc5..10d2aef9 100644 --- a/examples/src/main/java/com/demcha/examples/flagships/MasterShowcaseExample.java +++ b/examples/src/main/java/com/demcha/examples/flagships/MasterShowcaseExample.java @@ -57,294 +57,313 @@ private MasterShowcaseExample() { public static Path generate() throws Exception { Path outputFile = ExampleOutputPaths.prepare("flagships", "master-showcase.pdf"); - try (DocumentSession document = GraphCompose.document(outputFile) + try (DocumentSession document = document(outputFile).create()) { + compose(document); + document.buildPdf(); + } + return outputFile; + } + + /** + * The example's canonical session setup, shared by the PDF {@link #generate()} + * and its PPTX twin so both emit from an identically configured document — + * the "one source, two formats" contract. + * + * @param outputFile the file the session writes to (.pdf or .pptx) + * @return the configured builder, ready to {@code create()} + */ + static GraphCompose.DocumentBuilder document(Path outputFile) { + return GraphCompose.document(outputFile) .pageSize(DocumentPageSize.A4) .pageBackground(THEME.pageBackground()) - .margin(48, 34, 48, 34) - .create()) { + .margin(48, 34, 48, 34); + } - document.metadata(DocumentMetadata.builder() - .title("GraphCompose master showcase") - .author("Jordan Rivera") - .subject("Comprehensive end-to-end demo of the v1.5 canonical surface") - .keywords("graphcompose, showcase, business, theme, rich-text, table, shape, transform, barcode") - .creator("GraphCompose Examples") - .producer("GraphCompose") - .build()); + /** + * Composes the master showcase into {@code document}. Shared by + * {@link #generate()} and its PPTX twin so both lay out identical geometry. + * + * @param document the open session to compose into + */ + static void compose(DocumentSession document) { - document.header(DocumentHeaderFooter.builder() - .zone(DocumentHeaderFooterZone.HEADER) - .leftText("GraphCompose · Master showcase") - .rightText("{date}") - .fontSize(9f) - .textColor(MUTED) - .showSeparator(true) - .separatorColor(THEME.palette().rule()) - .separatorThickness(0.5f) - .build()); + document.metadata(DocumentMetadata.builder() + .title("GraphCompose master showcase") + .author("Jordan Rivera") + .subject("Comprehensive end-to-end demo of the v1.5 canonical surface") + .keywords("graphcompose, showcase, business, theme, rich-text, table, shape, transform, barcode") + .creator("GraphCompose Examples") + .producer("GraphCompose") + .build()); - document.footer(DocumentHeaderFooter.builder() - .zone(DocumentHeaderFooterZone.FOOTER) - .leftText("v1.5 — \"intuitive\" release") - .rightText("Page {page} of {pages}") - .fontSize(9f) - .textColor(MUTED) - .showSeparator(true) - .separatorColor(THEME.palette().rule()) - .separatorThickness(0.5f) - .build()); + document.header(DocumentHeaderFooter.builder() + .zone(DocumentHeaderFooterZone.HEADER) + .leftText("GraphCompose · Master showcase") + .rightText("{date}") + .fontSize(9f) + .textColor(MUTED) + .showSeparator(true) + .separatorColor(THEME.palette().rule()) + .separatorThickness(0.5f) + .build()); - document.pageFlow() - .name("MasterShowcase") - .spacing(14) + document.footer(DocumentHeaderFooter.builder() + .zone(DocumentHeaderFooterZone.FOOTER) + .leftText("v1.5 — \"intuitive\" release") + .rightText("Page {page} of {pages}") + .fontSize(9f) + .textColor(MUTED) + .showSeparator(true) + .separatorColor(THEME.palette().rule()) + .separatorThickness(0.5f) + .build()); - // ───── Hero ───── - .addSection("Hero", section -> section - .softPanel(THEME.palette().surfaceMuted(), 14, 22) - .accentLeft(ACCENT, 5) - .spacing(8) - .addParagraph(p -> p - .text("Q2 SAMPLE REPORT") - .textStyle(label()) - .margin(DocumentInsets.zero())) - .addParagraph(p -> p - .text("Composing real documents with GraphCompose") - .textStyle(THEME.text().h1()) - .margin(DocumentInsets.zero())) - .addRich(rich -> rich - .plain("A single Java DSL describes intent — ") - .bold("modules, sections, rows, tables, shapes, layers") - .plain(" — and the engine resolves layout, pagination, and ") - .accent("PDFBox rendering", BRAND) - .plain(" deterministically. This document is generated by one ") - .style("MasterShowcaseExample.java", DocumentTextStyle.builder() - .fontName(FontName.COURIER) - .size(9.5) - .color(BRAND) - .build()) - .plain(" file."))) + document.pageFlow() + .name("MasterShowcase") + .spacing(14) - // ───── Hero badge row: shape container with rotated outline + branded QR ───── - .addRow("HeroBadges", row -> row - .spacing(18) - .weights(2, 1, 1) - .addSection("Highlight", section -> section - .softPanel(DocumentColor.WHITE, 10, 16) - .stroke(DocumentStroke.of(THEME.palette().rule(), 0.6)) - .accentLeft(ACCENT, 3) - .spacing(6) - .addParagraph(p -> p - .text("Highlight") - .textStyle(panelHeadline()) - .margin(DocumentInsets.zero())) - .addRich(rich -> rich - .plain("Status: ") - .bold("On track") - .plain(" — ") - .accent("675 / 675 tests green", BRAND) - .plain(" — Q2 release window confirmed for ") - .underline("June 2026") - .plain(".")) - .addRich(rich -> rich - .plain("Performance: ") - .color("invoice-template", BRAND_DEEP) - .plain(" 13.4 ms avg, 75 docs/sec; ") - .color("feature-rich", BRAND_DEEP) - .plain(" 36.8 ms avg, 27 docs/sec."))) - .addSection("Seal", section -> section - .padding(DocumentInsets.of(2)) - .addCircle(118, BRAND, circle -> circle - .name("RotatedSeal") - .padding(8) - .stroke(DocumentStroke.of(ACCENT, 1.5)) - .rotate(-12) - .clipPolicy(ClipPolicy.CLIP_PATH) - .center(label("APPROVED", - style(FontName.HELVETICA_BOLD, 13, - DocumentTextDecoration.BOLD, - DocumentColor.WHITE))) - .position(label("Q2 / 2026", - style(FontName.HELVETICA_BOLD, 7.5, - DocumentTextDecoration.BOLD, - SOFT_GOLD)), - 0, -16, LayerAlign.BOTTOM_CENTER))) - .addSection("Code", section -> section - .softPanel(PALE_TEAL, 8, 10) - .spacing(5) - .addParagraph(p -> p - .text("Audit ID") - .textStyle(label()) - .margin(DocumentInsets.zero())) - .addBarcode(barcode -> barcode - .qrCode() - .data("https://github.com/DemchaAV/GraphCompose") - .foreground(BRAND) - .background(PALE_TEAL) - .quietZone(2) - .size(106, 106)) - .addParagraph(p -> p - .text("DemchaAV/GraphCompose") - .textStyle(monoCaption()) - .margin(DocumentInsets.zero())))) + // ───── Hero ───── + .addSection("Hero", section -> section + .softPanel(THEME.palette().surfaceMuted(), 14, 22) + .accentLeft(ACCENT, 5) + .spacing(8) + .addParagraph(p -> p + .text("Q2 SAMPLE REPORT") + .textStyle(label()) + .margin(DocumentInsets.zero())) + .addParagraph(p -> p + .text("Composing real documents with GraphCompose") + .textStyle(THEME.text().h1()) + .margin(DocumentInsets.zero())) + .addRich(rich -> rich + .plain("A single Java DSL describes intent — ") + .bold("modules, sections, rows, tables, shapes, layers") + .plain(" — and the engine resolves layout, pagination, and ") + .accent("PDFBox rendering", BRAND) + .plain(" deterministically. This document is generated by one ") + .style("MasterShowcaseExample.java", DocumentTextStyle.builder() + .fontName(FontName.COURIER) + .size(9.5) + .color(BRAND) + .build()) + .plain(" file."))) - // ───── Executive summary ───── - .addSection("Summary", section -> section - .softPanel(DocumentColor.WHITE, 10, 16) - .stroke(DocumentStroke.of(THEME.palette().rule(), 0.5)) - .accentLeft(BRAND, 3) - .spacing(8) - .addParagraph(p -> p - .text("Executive summary") - .textStyle(THEME.text().h2()) - .margin(DocumentInsets.zero())) - .addRich(rich -> rich - .plain("v1.5 lands the cinematic features that turn GraphCompose from \"tidy PDF layouter\" into a designed-document engine: ") - .bold("shape-as-container with clip path") - .plain(", ") - .bold("rotate / scale + per-layer z-index") - .plain(", ") - .bold("advanced tables (row span, zebra, totals, repeating header)") - .plain(", and two cinematic templates ") - .accent("ModernInvoice / ModernProposal", BRAND) - .plain(" driven by ") - .accent("BrandTheme", BRAND) - .plain("."))) + // ───── Hero badge row: shape container with rotated outline + branded QR ───── + .addRow("HeroBadges", row -> row + .spacing(18) + .weights(2, 1, 1) + .addSection("Highlight", section -> section + .softPanel(DocumentColor.WHITE, 10, 16) + .stroke(DocumentStroke.of(THEME.palette().rule(), 0.6)) + .accentLeft(ACCENT, 3) + .spacing(6) + .addParagraph(p -> p + .text("Highlight") + .textStyle(panelHeadline()) + .margin(DocumentInsets.zero())) + .addRich(rich -> rich + .plain("Status: ") + .bold("On track") + .plain(" — ") + .accent("675 / 675 tests green", BRAND) + .plain(" — Q2 release window confirmed for ") + .underline("June 2026") + .plain(".")) + .addRich(rich -> rich + .plain("Performance: ") + .color("invoice-template", BRAND_DEEP) + .plain(" 13.4 ms avg, 75 docs/sec; ") + .color("feature-rich", BRAND_DEEP) + .plain(" 36.8 ms avg, 27 docs/sec."))) + .addSection("Seal", section -> section + .padding(DocumentInsets.of(2)) + .addCircle(118, BRAND, circle -> circle + .name("RotatedSeal") + .padding(8) + .stroke(DocumentStroke.of(ACCENT, 1.5)) + .rotate(-12) + .clipPolicy(ClipPolicy.CLIP_PATH) + .center(label("APPROVED", + style(FontName.HELVETICA_BOLD, 13, + DocumentTextDecoration.BOLD, + DocumentColor.WHITE))) + .position(label("Q2 / 2026", + style(FontName.HELVETICA_BOLD, 7.5, + DocumentTextDecoration.BOLD, + SOFT_GOLD)), + 0, -16, LayerAlign.BOTTOM_CENTER))) + .addSection("Code", section -> section + .softPanel(PALE_TEAL, 8, 10) + .spacing(5) + .addParagraph(p -> p + .text("Audit ID") + .textStyle(label()) + .margin(DocumentInsets.zero())) + .addBarcode(barcode -> barcode + .qrCode() + .data("https://github.com/DemchaAV/GraphCompose") + .foreground(BRAND) + .background(PALE_TEAL) + .quietZone(2) + .size(106, 106)) + .addParagraph(p -> p + .text("DemchaAV/GraphCompose") + .textStyle(monoCaption()) + .margin(DocumentInsets.zero())))) - // ───── Quarterly numbers — full advanced table ───── - .addSection("Numbers", section -> section - .padding(DocumentInsets.zero()) - .spacing(6) - .addParagraph(p -> p - .text("Quarterly numbers") - .textStyle(THEME.text().h2()) - .margin(DocumentInsets.zero())) - .addParagraph(p -> p - .text("Repeated header + zebra rows + bold totals; the table paginates row-by-row across the next page so column titles stay visible.") - .textStyle(caption()) - .lineSpacing(1.4) - .margin(new DocumentInsets(0, 0, 6, 0)))) + // ───── Executive summary ───── + .addSection("Summary", section -> section + .softPanel(DocumentColor.WHITE, 10, 16) + .stroke(DocumentStroke.of(THEME.palette().rule(), 0.5)) + .accentLeft(BRAND, 3) + .spacing(8) + .addParagraph(p -> p + .text("Executive summary") + .textStyle(THEME.text().h2()) + .margin(DocumentInsets.zero())) + .addRich(rich -> rich + .plain("v1.5 lands the cinematic features that turn GraphCompose from \"tidy PDF layouter\" into a designed-document engine: ") + .bold("shape-as-container with clip path") + .plain(", ") + .bold("rotate / scale + per-layer z-index") + .plain(", ") + .bold("advanced tables (row span, zebra, totals, repeating header)") + .plain(", and two cinematic templates ") + .accent("ModernInvoice / ModernProposal", BRAND) + .plain(" driven by ") + .accent("BrandTheme", BRAND) + .plain("."))) - .addTable(table -> { - table.name("QuarterlyTable") - .columns( - DocumentTableColumn.fixed(60), - DocumentTableColumn.auto(), - DocumentTableColumn.fixed(80), - DocumentTableColumn.fixed(80), - DocumentTableColumn.fixed(80)) - .defaultCellStyle(DocumentTableStyle.builder() - .padding(new DocumentInsets(7, 9, 7, 9)) - .textStyle(DocumentTextStyle.builder() - .fontName(FontName.HELVETICA) - .size(9.5) - .color(INK) - .build()) - .stroke(DocumentStroke.of(THEME.palette().rule(), 0.4)) - .build()) - .headerStyle(DocumentTableStyle.builder() - .padding(new DocumentInsets(8, 9, 8, 9)) - .textStyle(DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) - .size(9.5) - .color(DocumentColor.WHITE) - .build()) - .fillColor(BRAND_DEEP) - .stroke(DocumentStroke.of(BRAND_DEEP, 0.4)) - .build()) - .headerRow("Code", "Initiative", "Hours", "Owner", "Amount") - .repeatHeader() - .zebra(DocumentColor.WHITE, ZEBRA); - for (int i = 0; i < 24; i++) { - table.row( - String.format("INV-%03d", 100 + i), - "Initiative " + (i + 1) + " — quarterly delivery slice", - String.format("%d h", 40 + i * 3), - i % 3 == 0 ? "Studio" : i % 3 == 1 ? "Audit" : "Build", - String.format("£ %,d", 1_800 + i * 215)); - } - table.totalRow(DocumentTableStyle.builder() - .padding(new DocumentInsets(8, 9, 8, 9)) - .textStyle(DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) - .size(10) - .color(DocumentColor.WHITE) - .build()) - .fillColor(TOTAL_FILL) - .stroke(DocumentStroke.of(TOTAL_FILL, 0.4)) - .build(), - "TOT", "Total quarterly spend", "—", "—", "£ 102,720"); - }) + // ───── Quarterly numbers — full advanced table ───── + .addSection("Numbers", section -> section + .padding(DocumentInsets.zero()) + .spacing(6) + .addParagraph(p -> p + .text("Quarterly numbers") + .textStyle(THEME.text().h2()) + .margin(DocumentInsets.zero())) + .addParagraph(p -> p + .text("Repeated header + zebra rows + bold totals; the table paginates row-by-row across the next page so column titles stay visible.") + .textStyle(caption()) + .lineSpacing(1.4) + .margin(new DocumentInsets(0, 0, 6, 0)))) - // ───── Highlights row: layered card + accent rules ───── - .addRow("HighlightsRow", row -> row - .spacing(14) - .weights(1, 1, 1) - .addSection("Card1", section -> highlightCard(section, - "Cinematic templates", - "ModernInvoice", - "Same data renders through a BrandTheme — pass a different theme to create(...), ship a new look.")) - .addSection("Card2", section -> highlightCard(section, - "Shape-as-container", - "addCircle + clip path", - "ClipPolicy.CLIP_PATH clips children to the actual outline — DOCX falls back inline with a one-time capability warning.")) - .addSection("Card3", section -> highlightCard(section, - "Transformable mixin", - "rotate / scale on every shape", - "v1.5 extends Transformable to every leaf builder so any shape rotates around its placement centre."))) - - // ───── Action items + status legend ───── - .addSection("ActionItems", section -> section - .softPanel(DocumentColor.WHITE, 8, 14) - .stroke(DocumentStroke.of(THEME.palette().rule(), 0.5)) - .accentLeft(BRAND, 3) - .accentBottom(ACCENT, 2) - .spacing(8) - .addParagraph(p -> p - .text("Action items") - .textStyle(THEME.text().h2()) - .margin(DocumentInsets.zero())) - .addRich(rich -> rich - .plain("• Tag ") - .bold("v1.5.0") - .plain(" on main once develop is merged through PR. ") - .accent("Owner: maintainer", BRAND) - .plain(".")) - .addRich(rich -> rich - .plain("• Open ") - .bold("ADR 0003") - .plain(" for Phase A nested-list ergonomics. ") - .accent("Owner: contributor", BRAND) - .plain(".")) - .addRich(rich -> rich - .plain("• Ship Phase B composed-cell content ahead of Phase C canvas. ") - .accent("Owner: contributor", BRAND) - .plain("."))) + .addTable(table -> { + table.name("QuarterlyTable") + .columns( + DocumentTableColumn.fixed(60), + DocumentTableColumn.auto(), + DocumentTableColumn.fixed(80), + DocumentTableColumn.fixed(80), + DocumentTableColumn.fixed(80)) + .defaultCellStyle(DocumentTableStyle.builder() + .padding(new DocumentInsets(7, 9, 7, 9)) + .textStyle(DocumentTextStyle.builder() + .fontName(FontName.HELVETICA) + .size(9.5) + .color(INK) + .build()) + .stroke(DocumentStroke.of(THEME.palette().rule(), 0.4)) + .build()) + .headerStyle(DocumentTableStyle.builder() + .padding(new DocumentInsets(8, 9, 8, 9)) + .textStyle(DocumentTextStyle.builder() + .fontName(FontName.HELVETICA_BOLD) + .size(9.5) + .color(DocumentColor.WHITE) + .build()) + .fillColor(BRAND_DEEP) + .stroke(DocumentStroke.of(BRAND_DEEP, 0.4)) + .build()) + .headerRow("Code", "Initiative", "Hours", "Owner", "Amount") + .repeatHeader() + .zebra(DocumentColor.WHITE, ZEBRA); + for (int i = 0; i < 24; i++) { + table.row( + String.format("INV-%03d", 100 + i), + "Initiative " + (i + 1) + " — quarterly delivery slice", + String.format("%d h", 40 + i * 3), + i % 3 == 0 ? "Studio" : i % 3 == 1 ? "Audit" : "Build", + String.format("£ %,d", 1_800 + i * 215)); + } + table.totalRow(DocumentTableStyle.builder() + .padding(new DocumentInsets(8, 9, 8, 9)) + .textStyle(DocumentTextStyle.builder() + .fontName(FontName.HELVETICA_BOLD) + .size(10) + .color(DocumentColor.WHITE) + .build()) + .fillColor(TOTAL_FILL) + .stroke(DocumentStroke.of(TOTAL_FILL, 0.4)) + .build(), + "TOT", "Total quarterly spend", "—", "—", "£ 102,720"); + }) - // ───── Footer audit code ───── - .addRow("FooterRow", row -> row - .spacing(14) - .weights(2, 1) - .addSection("FooterText", section -> section - .accentTop(THEME.palette().rule(), 0.6) - .padding(new DocumentInsets(8, 0, 0, 0)) - .addParagraph(p -> p - .text("Source: examples/.../MasterShowcaseExample.java — every visible token routes through BusinessTheme.modern() so the same source renders any of classic / modern / executive themes.") - .textStyle(caption()) - .lineSpacing(1.4) - .margin(DocumentInsets.zero()))) - .addSection("FooterCode", section -> section - .padding(DocumentInsets.zero()) - .addBarcode(barcode -> barcode - .code128() - .data("GC-MASTER-Q2-2026") - .foreground(BRAND) - .size(180, 36)))) - .build(); + // ───── Highlights row: layered card + accent rules ───── + .addRow("HighlightsRow", row -> row + .spacing(14) + .weights(1, 1, 1) + .addSection("Card1", section -> highlightCard(section, + "Cinematic templates", + "ModernInvoice", + "Same data renders through a BrandTheme — pass a different theme to create(...), ship a new look.")) + .addSection("Card2", section -> highlightCard(section, + "Shape-as-container", + "addCircle + clip path", + "ClipPolicy.CLIP_PATH clips children to the actual outline — DOCX falls back inline with a one-time capability warning.")) + .addSection("Card3", section -> highlightCard(section, + "Transformable mixin", + "rotate / scale on every shape", + "v1.5 extends Transformable to every leaf builder so any shape rotates around its placement centre."))) - document.buildPdf(); - } + // ───── Action items + status legend ───── + .addSection("ActionItems", section -> section + .softPanel(DocumentColor.WHITE, 8, 14) + .stroke(DocumentStroke.of(THEME.palette().rule(), 0.5)) + .accentLeft(BRAND, 3) + .accentBottom(ACCENT, 2) + .spacing(8) + .addParagraph(p -> p + .text("Action items") + .textStyle(THEME.text().h2()) + .margin(DocumentInsets.zero())) + .addRich(rich -> rich + .plain("• Tag ") + .bold("v1.5.0") + .plain(" on main once develop is merged through PR. ") + .accent("Owner: maintainer", BRAND) + .plain(".")) + .addRich(rich -> rich + .plain("• Open ") + .bold("ADR 0003") + .plain(" for Phase A nested-list ergonomics. ") + .accent("Owner: contributor", BRAND) + .plain(".")) + .addRich(rich -> rich + .plain("• Ship Phase B composed-cell content ahead of Phase C canvas. ") + .accent("Owner: contributor", BRAND) + .plain("."))) - return outputFile; + // ───── Footer audit code ───── + .addRow("FooterRow", row -> row + .spacing(14) + .weights(2, 1) + .addSection("FooterText", section -> section + .accentTop(THEME.palette().rule(), 0.6) + .padding(new DocumentInsets(8, 0, 0, 0)) + .addParagraph(p -> p + .text("Source: examples/.../MasterShowcaseExample.java — every visible token routes through BusinessTheme.modern() so the same source renders any of classic / modern / executive themes.") + .textStyle(caption()) + .lineSpacing(1.4) + .margin(DocumentInsets.zero()))) + .addSection("FooterCode", section -> section + .padding(DocumentInsets.zero()) + .addBarcode(barcode -> barcode + .code128() + .data("GC-MASTER-Q2-2026") + .foreground(BRAND) + .size(180, 36)))) + .build(); } public static void main(String[] args) throws Exception { diff --git a/examples/src/main/java/com/demcha/examples/flagships/MasterShowcasePptxExample.java b/examples/src/main/java/com/demcha/examples/flagships/MasterShowcasePptxExample.java new file mode 100644 index 00000000..95126f9c --- /dev/null +++ b/examples/src/main/java/com/demcha/examples/flagships/MasterShowcasePptxExample.java @@ -0,0 +1,49 @@ +package com.demcha.examples.flagships; + +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.examples.support.ExampleOutputPaths; + +import java.nio.file.Path; + +/** + * The {@link MasterShowcaseExample} kitchen-sink report emitted as a PowerPoint + * deck: the same composition, built once through the shared session config and + * written through the fixed-layout PPTX backend via + * {@link DocumentSession#buildPptx(java.nio.file.Path)}. Each resolved page becomes one + * identically-sized slide, so the multi-page report opens in PowerPoint as an + * editable copy of the PDF — rich text, the advanced table, header/footer + * chrome, and barcodes arrive as native shapes and text frames; the rotated, + * clip-masked seal is the one region that lands as a pixel-exact picture. + * + * @since 2.1.0 + */ +public final class MasterShowcasePptxExample { + + private MasterShowcasePptxExample() { + } + + /** + * Builds the report as the .pptx twin of {@link MasterShowcaseExample}. + * + * @return the generated file path + * @throws Exception when composition or rendering fails + */ + public static Path generate() throws Exception { + Path outputFile = ExampleOutputPaths.prepare("flagships", "master-showcase.pptx"); + try (DocumentSession document = MasterShowcaseExample.document(outputFile).create()) { + MasterShowcaseExample.compose(document); + document.buildPptx(outputFile); + } + 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()); + } +} diff --git a/examples/src/main/java/com/demcha/examples/support/ShowcaseMetadata.java b/examples/src/main/java/com/demcha/examples/support/ShowcaseMetadata.java index b9da7826..9382a7ad 100644 --- a/examples/src/main/java/com/demcha/examples/support/ShowcaseMetadata.java +++ b/examples/src/main/java/com/demcha/examples/support/ShowcaseMetadata.java @@ -244,10 +244,15 @@ private static Entry entry(String title, String desc, List tags, String return new Entry(title, desc, tags, code); } + /** + * Prefixes the category onto an entry's own tags, keeping first-seen order + * and dropping repeats. Most call sites pass their group again as a tag, + * which used to render the same chip twice on a card. + */ private static List withCategory(String category, String... extra) { - List tags = new java.util.ArrayList<>(); + java.util.Set tags = new java.util.LinkedHashSet<>(); tags.add(category); - for (String t : extra) tags.add(t); + java.util.Collections.addAll(tags, extra); return List.copyOf(tags); } } diff --git a/examples/src/main/java/com/demcha/examples/support/ShowcaseSync.java b/examples/src/main/java/com/demcha/examples/support/ShowcaseSync.java index e23a892c..ad0310c1 100644 --- a/examples/src/main/java/com/demcha/examples/support/ShowcaseSync.java +++ b/examples/src/main/java/com/demcha/examples/support/ShowcaseSync.java @@ -33,6 +33,11 @@ *
  • Renders page 0 of the PDF to a {@code 1.5x} PNG and writes it to * {@code web/showcase/screenshots///.png} * — the static site uses these as preview thumbnails.
  • + *
  • Copies a same-named {@code .pptx} sitting beside the PDF to + * {@code web/showcase/pptx///.pptx}, published as a + * second download on the same card. The twin shares the PDF's preview: + * both backends draw the same resolved layout, and POI has no page + * rasterizer to build a separate thumbnail with.
  • * * *

    Then it writes a structured {@code web/examples.json} manifest @@ -72,6 +77,7 @@ public static void main(String[] args) throws Exception { Map>> tree = new TreeMap<>(); int copied = 0; int rendered = 0; + int decks = 0; try (Stream walk = Files.walk(generatedPdfs)) { List pdfs = walk @@ -107,6 +113,23 @@ public static void main(String[] args) throws Exception { renderPreview(pdf, pngTarget); rendered++; + // A twin flagship renders the same composition to a deck beside its + // PDF. The deck is published as a second download on the same card: + // it shares the PDF's preview by construction — both backends draw + // the same resolved layout — and POI has no page rasterizer to make + // a separate one with. + String pptxUrl = null; + Path pptxSource = pdf.resolveSibling(basename + ".pptx"); + if (Files.isRegularFile(pptxSource)) { + Path pptxTarget = showcaseRoot.resolve("pptx").resolve(category).resolve(group) + .resolve(basename + ".pptx"); + Files.createDirectories(pptxTarget.getParent()); + Files.copy(pptxSource, pptxTarget, StandardCopyOption.REPLACE_EXISTING); + copied++; + decks++; + pptxUrl = relativeUrl(showcaseRoot, pptxTarget, siteRoot); + } + ShowcaseMetadata.Entry meta = ShowcaseMetadata.lookup(basename, category, group); ManifestEntry entry = new ManifestEntry( basename, @@ -114,6 +137,7 @@ public static void main(String[] args) throws Exception { meta.description(), meta.tags(), relativeUrl(showcaseRoot, pdfTarget, siteRoot), + pptxUrl, relativeUrl(showcaseRoot, pngTarget, siteRoot), meta.codeUrl()); tree.computeIfAbsent(category, c -> new TreeMap<>()) @@ -131,8 +155,8 @@ public static void main(String[] args) throws Exception { String json = renderManifest(tree); Files.writeString(manifestFile, json); - System.out.println("Synced " + copied + " PDFs and " + rendered - + " preview PNGs into " + showcaseRoot); + System.out.println("Synced " + copied + " documents (" + rendered + " PDFs, " + + decks + " PPTX twins) and " + rendered + " preview PNGs into " + showcaseRoot); System.out.println("Wrote manifest to " + manifestFile); } @@ -262,12 +286,17 @@ private static String jsonString(String s) { return sb.toString(); } + /** + * One showcase card. {@code pptx} is {@code null} for the majority of + * examples that render PDF only; the twin flagships carry both. + */ private record ManifestEntry( String id, String title, String description, List tags, String pdf, + String pptx, String screenshot, String code) { @@ -283,6 +312,9 @@ String toJson() { } sb.append("],\n"); sb.append(" \"pdf\": ").append(jsonString(pdf)).append(",\n"); + if (pptx != null) { + sb.append(" \"pptx\": ").append(jsonString(pptx)).append(",\n"); + } sb.append(" \"screenshot\": ").append(jsonString(screenshot)).append(",\n"); sb.append(" \"code\": ").append(jsonString(code)).append("\n"); sb.append(" }"); diff --git a/examples/src/test/java/com/demcha/examples/flagships/FlagshipPptxTwinNativeShapeTest.java b/examples/src/test/java/com/demcha/examples/flagships/FlagshipPptxTwinNativeShapeTest.java new file mode 100644 index 00000000..c4ac080c --- /dev/null +++ b/examples/src/test/java/com/demcha/examples/flagships/FlagshipPptxTwinNativeShapeTest.java @@ -0,0 +1,97 @@ +package com.demcha.examples.flagships; + +import com.demcha.compose.document.api.DocumentSession; +import org.apache.poi.xslf.usermodel.XMLSlideShow; +import org.apache.poi.xslf.usermodel.XSLFPictureShape; +import org.apache.poi.xslf.usermodel.XSLFShape; +import org.apache.poi.xslf.usermodel.XSLFSlide; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayInputStream; +import java.nio.file.Path; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Guards that the flagship PDF documents re-emitted as PowerPoint decks land as + * native, editable shapes — not a page-sized screenshot. Each twin shares its + * source example's {@code compose(...)} and {@code document(...)} session + * config, so the same {@code LayoutGraph} that prints the PDF is what the deck + * is built from. The test opens the resulting {@code .pptx} with Apache POI and + * asserts native shapes dominate, with only the genuinely un-vectorizable + * regions (a clip-masked composite, a photo, a barcode raster) landing as + * pictures. A clip-safety or backend regression that rasterised whole slides + * would silently falsify the "editable in PowerPoint" claim — this catches it. + */ +class FlagshipPptxTwinNativeShapeTest { + + @Test + void businessReportDeckStaysNative() throws Exception { + byte[] deck; + try (DocumentSession document = + BusinessReportExample.document(Path.of("target", "business-report-native.pptx")).create()) { + BusinessReportExample.compose(document); + deck = document.toPptxBytes(); + } + assertMostlyNative(deck, 1, 2, 60); + } + + @Test + void financialReportDeckStaysNative() throws Exception { + byte[] deck; + try (DocumentSession document = + FinancialReportExample.document(Path.of("target", "financial-report-native.pptx")).create()) { + FinancialReportExample.compose(document); + deck = document.toPptxBytes(); + } + // The masthead photo is a genuine bitmap; everything else stays native. + assertMostlyNative(deck, 1, 2, 60); + } + + @Test + void masterShowcaseDeckStaysNative() throws Exception { + byte[] deck; + try (DocumentSession document = + MasterShowcaseExample.document(Path.of("target", "master-showcase-native.pptx")).create()) { + MasterShowcaseExample.compose(document); + deck = document.toPptxBytes(); + } + // Multi-page report → one slide per page; the rotated clip-masked seal + // and the two ZXing barcodes are the only expected pictures. + assertMostlyNative(deck, 2, 4, 120); + } + + /** + * Opens the deck and asserts it is built from native shapes. + * + * @param deck the rendered {@code .pptx} bytes + * @param minSlides one slide per resolved page — at least this many + * @param maxPictures the un-vectorizable regions that may rasterise + * @param minNativeShapes native (non-picture) shapes summed over every slide + */ + private static void assertMostlyNative(byte[] deck, int minSlides, int maxPictures, + int minNativeShapes) throws Exception { + try (XMLSlideShow show = new XMLSlideShow(new ByteArrayInputStream(deck))) { + List slides = show.getSlides(); + assertThat(slides.size()) + .as("one slide per resolved page") + .isGreaterThanOrEqualTo(minSlides); + + long pictures = 0; + long nativeShapes = 0; + for (XSLFSlide slide : slides) { + List shapes = slide.getShapes(); + long pics = shapes.stream().filter(XSLFPictureShape.class::isInstance).count(); + pictures += pics; + nativeShapes += shapes.size() - pics; + } + assertThat(pictures) + .as("only genuinely un-vectorizable regions rasterise") + .isLessThanOrEqualTo(maxPictures); + assertThat(nativeShapes) + .as("the document lands as native, editable shapes, not a screenshot") + .isGreaterThan(minNativeShapes); + } + } +} diff --git a/web/examples.js b/web/examples.js index 80691fff..03edd4f1 100644 --- a/web/examples.js +++ b/web/examples.js @@ -353,6 +353,7 @@ .map(t => '' + escHtml(t) + '').join(''); const screenshot = ex.screenshot || ''; const pdf = ex.pdf || ''; + const pptx = ex.pptx || ''; const code = ex.code || '#'; const altText = buildAlt(ex); return [ @@ -375,6 +376,12 @@ tags ? '

    ' + tags + '
    ' : '', '
    ', ' View PDF', + // A twin flagship also ships the deck the same session emitted. Only the + // examples that actually rendered one carry `pptx`, so the link is omitted + // rather than dead for every PDF-only card. + pptx + ? ' Get PPTX' + : '', ' View Code', '
    ', ' ', diff --git a/web/examples.json b/web/examples.json index a57b8b01..8497b08f 100644 --- a/web/examples.json +++ b/web/examples.json @@ -162,7 +162,7 @@ "id": "project-proposal-cinematic", "title": "Project Proposal (cinematic)", "description": "End-to-end project proposal with mountain hero, scope panels, and pricing summary.", - "tags": ["proposal", "proposal", "cinematic"], + "tags": ["proposal", "cinematic"], "pdf": "showcase/pdf/templates/proposal/project-proposal-cinematic.pdf", "screenshot": "showcase/screenshots/templates/proposal/project-proposal-cinematic.png", "code": "https://github.com/DemchaAV/GraphCompose/blob/v2.0.0/examples/src/main/java/com/demcha/examples/templates/proposal/CinematicProposalFileExample.java" @@ -171,7 +171,7 @@ "id": "proposal-cinematic", "title": "Cinematic Proposal", "description": "Layered ModernProposal layout with cover panel, hero spread, and rich typography.", - "tags": ["proposal", "proposal", "cinematic"], + "tags": ["proposal", "cinematic"], "pdf": "showcase/pdf/templates/proposal/proposal-cinematic.pdf", "screenshot": "showcase/screenshots/templates/proposal/proposal-cinematic.png", "code": "https://github.com/DemchaAV/GraphCompose/blob/v2.0.0/examples/src/main/java/com/demcha/examples/templates/proposal/ProposalCinematicFileExample.java" @@ -195,7 +195,7 @@ "id": "invoice-cinematic", "title": "Cinematic Invoice", "description": "Layered ModernInvoice preset with theme-driven layout, advanced tables, and totals.", - "tags": ["invoice", "invoice", "cinematic"], + "tags": ["invoice", "cinematic"], "pdf": "showcase/pdf/templates/invoice/invoice-cinematic.pdf", "screenshot": "showcase/screenshots/templates/invoice/invoice-cinematic.png", "code": "https://github.com/DemchaAV/GraphCompose/blob/v2.0.0/examples/src/main/java/com/demcha/examples/templates/invoice/InvoiceCinematicFileExample.java" @@ -219,7 +219,7 @@ "id": "weekly-schedule", "title": "Weekly Schedule", "description": "Multi-day weekly schedule with shift assignments, category fills, and repeated header.", - "tags": ["schedule", "schedule", "table"], + "tags": ["schedule", "table"], "pdf": "showcase/pdf/templates/schedule/weekly-schedule.pdf", "screenshot": "showcase/screenshots/templates/schedule/weekly-schedule.png", "code": "https://github.com/DemchaAV/GraphCompose/blob/v2.0.0/examples/src/main/java/com/demcha/examples/templates/schedule/WeeklyScheduleFileExample.java" @@ -381,7 +381,7 @@ "id": "canvas-layer-showcase", "title": "Canvas Layer (free-canvas)", "description": "CanvasLayerNode — pixel-precise (x,y) placement of children inside a fixed bounding box.", - "tags": ["canvas", "canvas", "v1.6", "absolute"], + "tags": ["canvas", "v1.6", "absolute"], "pdf": "showcase/pdf/features/canvas/canvas-layer-showcase.pdf", "screenshot": "showcase/screenshots/features/canvas/canvas-layer-showcase.png", "code": "https://github.com/DemchaAV/GraphCompose/blob/v2.0.0/examples/src/main/java/com/demcha/examples/features/canvas/CanvasLayerExample.java" @@ -396,7 +396,7 @@ "id": "composed-table-cell-showcase", "title": "Composed Table Cells", "description": "DocumentTableCell.node(DocumentNode) — paragraphs, lists, sub-tables inside cells with two-pass measurement.", - "tags": ["tables", "tables", "v1.6"], + "tags": ["tables", "v1.6"], "pdf": "showcase/pdf/features/tables/composed-table-cell-showcase.pdf", "screenshot": "showcase/screenshots/features/tables/composed-table-cell-showcase.png", "code": "https://github.com/DemchaAV/GraphCompose/blob/v2.0.0/examples/src/main/java/com/demcha/examples/features/tables/ComposedTableCellExample.java" @@ -414,7 +414,7 @@ "id": "table-advanced", "title": "Advanced Tables", "description": "Row span, column span, zebra rows, total rows, and repeating headers across page breaks.", - "tags": ["tables", "tables", "pagination"], + "tags": ["tables", "pagination"], "pdf": "showcase/pdf/features/tables/table-advanced.pdf", "screenshot": "showcase/screenshots/features/tables/table-advanced.png", "code": "https://github.com/DemchaAV/GraphCompose/blob/v2.0.0/examples/src/main/java/com/demcha/examples/features/tables/TableAdvancedExample.java" @@ -429,7 +429,7 @@ "id": "nested-list-showcase", "title": "Nested Lists", "description": "ListBuilder.addItem(label, Consumer) — depth cascade, per-depth markers, mixed flat / nested authoring.", - "tags": ["lists", "lists", "v1.6"], + "tags": ["lists", "v1.6"], "pdf": "showcase/pdf/features/lists/nested-list-showcase.pdf", "screenshot": "showcase/screenshots/features/lists/nested-list-showcase.png", "code": "https://github.com/DemchaAV/GraphCompose/blob/v2.0.0/examples/src/main/java/com/demcha/examples/features/lists/NestedListExample.java" @@ -444,7 +444,7 @@ "id": "line-cap", "title": "Line Caps & Dotted Lines", "description": "addLine(...).lineCap(DocumentLineCap.ROUND) — round/square end-caps on plain lines; a ROUND cap on a near-zero dashed(0.1, 4) rule draws the classic dotted TOC leader.", - "tags": ["shapes", "shapes", "lines", "v1.9"], + "tags": ["shapes", "lines", "v1.9"], "pdf": "showcase/pdf/features/shapes/line-cap.pdf", "screenshot": "showcase/screenshots/features/shapes/line-cap.png", "code": "https://github.com/DemchaAV/GraphCompose/blob/v2.0.0/examples/src/main/java/com/demcha/examples/features/shapes/LineCapExample.java" @@ -453,7 +453,7 @@ "id": "line-fill", "title": "Fill Lines & Dot Leaders", "description": "line().fill() — a rule or dotted leader stretches to the content width or its weighted row slot, drawing table-of-contents leaders without measuring the gap by hand.", - "tags": ["shapes", "shapes", "layout", "v1.9"], + "tags": ["shapes", "layout", "v1.9"], "pdf": "showcase/pdf/features/shapes/line-fill.pdf", "screenshot": "showcase/screenshots/features/shapes/line-fill.png", "code": "https://github.com/DemchaAV/GraphCompose/blob/v2.0.0/examples/src/main/java/com/demcha/examples/features/shapes/LineFillExample.java" @@ -462,7 +462,7 @@ "id": "photo-clip", "title": "Photo Clip (silhouette)", "description": "A raster photo clipped to a free-form silhouette — circle, SVG heart, star — via ShapeContainer.path(...) + ClipPolicy.CLIP_PATH; the image COVER-fills each box so the native-curve outline crops it crisply at any zoom.", - "tags": ["shapes", "shapes", "clip", "v1.8"], + "tags": ["shapes", "clip", "v1.8"], "pdf": "showcase/pdf/features/shapes/photo-clip.pdf", "screenshot": "showcase/screenshots/features/shapes/photo-clip.png", "code": "https://github.com/DemchaAV/GraphCompose/blob/v2.0.0/examples/src/main/java/com/demcha/examples/features/shapes/PhotoClipExample.java" @@ -471,7 +471,7 @@ "id": "shape-container", "title": "Shape-as-Container", "description": "Rounded rect, ellipse, circle containers with ClipPolicy and layered children.", - "tags": ["shapes", "shapes", "clip"], + "tags": ["shapes", "clip"], "pdf": "showcase/pdf/features/shapes/shape-container.pdf", "screenshot": "showcase/screenshots/features/shapes/shape-container.png", "code": "https://github.com/DemchaAV/GraphCompose/blob/v2.0.0/examples/src/main/java/com/demcha/examples/features/shapes/ShapeContainerExample.java" @@ -480,7 +480,7 @@ "id": "vector-path", "title": "Vector Paths (Bézier)", "description": "addPath(...) — free-form design shapes with native cubic Bézier curves: stroked waves, filled blobs, mixed line/curve ribbons. No tessellation.", - "tags": ["shapes", "shapes", "bezier", "v1.8"], + "tags": ["shapes", "bezier", "v1.8"], "pdf": "showcase/pdf/features/shapes/vector-path.pdf", "screenshot": "showcase/screenshots/features/shapes/vector-path.png", "code": "https://github.com/DemchaAV/GraphCompose/blob/v2.0.0/examples/src/main/java/com/demcha/examples/features/shapes/VectorPathExample.java" @@ -495,7 +495,7 @@ "id": "transforms", "title": "Layers + Transforms", "description": "rotate / scale on every leaf builder + LayerStack with explicit z-index.", - "tags": ["transforms", "transforms", "layers"], + "tags": ["transforms", "layers"], "pdf": "showcase/pdf/features/transforms/transforms.pdf", "screenshot": "showcase/screenshots/features/transforms/transforms.png", "code": "https://github.com/DemchaAV/GraphCompose/blob/v2.0.0/examples/src/main/java/com/demcha/examples/features/transforms/TransformsExample.java" @@ -573,7 +573,7 @@ "id": "rich-text-showcase", "title": "Rich Text", "description": "Inline runs with bold / italic / colour / link options, markdown parsing.", - "tags": ["text", "text", "rich"], + "tags": ["text", "rich"], "pdf": "showcase/pdf/features/text/rich-text-showcase.pdf", "screenshot": "showcase/screenshots/features/text/rich-text-showcase.png", "code": "https://github.com/DemchaAV/GraphCompose/blob/v2.0.0/examples/src/main/java/com/demcha/examples/features/text/RichTextShowcaseExample.java" @@ -582,7 +582,7 @@ "id": "section-presets", "title": "Section Presets", "description": "Pre-baked section bands, accent strips, soft panels for templates.", - "tags": ["text", "text", "sections"], + "tags": ["text", "sections"], "pdf": "showcase/pdf/features/text/section-presets.pdf", "screenshot": "showcase/screenshots/features/text/section-presets.png", "code": "https://github.com/DemchaAV/GraphCompose/blob/v2.0.0/examples/src/main/java/com/demcha/examples/features/text/SectionPresetsExample.java" @@ -597,7 +597,7 @@ "id": "barcode-showcase", "title": "Barcodes & QR", "description": "QR code, Code128, EAN-13, PDF417 — every supported barcode + per-barcode styling.", - "tags": ["barcodes", "barcodes", "qr"], + "tags": ["barcodes", "qr"], "pdf": "showcase/pdf/features/barcodes/barcode-showcase.pdf", "screenshot": "showcase/screenshots/features/barcodes/barcode-showcase.png", "code": "https://github.com/DemchaAV/GraphCompose/blob/v2.0.0/examples/src/main/java/com/demcha/examples/features/barcodes/BarcodeShowcaseExample.java" @@ -612,7 +612,7 @@ "id": "page-numbering", "title": "Page Numbering", "description": "DocumentHeaderFooter.numbering(DocumentPageNumbering) — {page}/{pages} footer tokens with countFrom(2) leaving the cover uncounted and LOWER_ROMAN restyling the body.", - "tags": ["chrome", "chrome", "footer", "v1.9"], + "tags": ["chrome", "footer", "v1.9"], "pdf": "showcase/pdf/features/chrome/page-numbering.pdf", "screenshot": "showcase/screenshots/features/chrome/page-numbering.png", "code": "https://github.com/DemchaAV/GraphCompose/blob/v2.0.0/examples/src/main/java/com/demcha/examples/features/chrome/PageNumberingExample.java" @@ -621,7 +621,7 @@ "id": "pdf-chrome", "title": "PDF Chrome", "description": "Headers, footers, watermarks, metadata, document protection / encryption.", - "tags": ["chrome", "chrome", "metadata", "watermark"], + "tags": ["chrome", "metadata", "watermark"], "pdf": "showcase/pdf/features/chrome/pdf-chrome.pdf", "screenshot": "showcase/screenshots/features/chrome/pdf-chrome.png", "code": "https://github.com/DemchaAV/GraphCompose/blob/v2.0.0/examples/src/main/java/com/demcha/examples/features/chrome/PdfChromeExample.java" @@ -630,7 +630,7 @@ "id": "viewer-preferences", "title": "Viewer Preferences", "description": "chrome().viewerPreferences(DocumentViewerPreferences) — opens the PDF with pageMode(USE_OUTLINES) showing the bookmark panel and displayDocTitle(true) in the title bar.", - "tags": ["chrome", "chrome", "v1.9"], + "tags": ["chrome", "v1.9"], "pdf": "showcase/pdf/features/chrome/viewer-preferences.pdf", "screenshot": "showcase/screenshots/features/chrome/viewer-preferences.png", "code": "https://github.com/DemchaAV/GraphCompose/blob/v2.0.0/examples/src/main/java/com/demcha/examples/features/chrome/ViewerPreferencesExample.java" @@ -645,7 +645,7 @@ "id": "invoice-http-stream", "title": "HTTP Streaming", "description": "Stream PDF directly to a Servlet response with no buffering.", - "tags": ["streaming", "streaming", "http"], + "tags": ["streaming", "http"], "pdf": "showcase/pdf/features/streaming/invoice-http-stream.pdf", "screenshot": "showcase/screenshots/features/streaming/invoice-http-stream.png", "code": "https://github.com/DemchaAV/GraphCompose/blob/v2.0.0/examples/src/main/java/com/demcha/examples/features/streaming/HttpStreamingExample.java" @@ -660,7 +660,7 @@ "id": "invoice-snapshot-regression", "title": "Layout Snapshots", "description": "How LayoutSnapshotAssertions captures the resolved layout graph for regression testing.", - "tags": ["snapshots", "snapshots", "testing"], + "tags": ["snapshots", "testing"], "pdf": "showcase/pdf/features/snapshots/invoice-snapshot-regression.pdf", "screenshot": "showcase/screenshots/features/snapshots/invoice-snapshot-regression.png", "code": "https://github.com/DemchaAV/GraphCompose/blob/v2.0.0/examples/src/main/java/com/demcha/examples/features/snapshots/LayoutSnapshotRegressionExample.java" @@ -675,7 +675,7 @@ "id": "chart-showcase", "title": "Vector Charts", "description": "section.chart(ChartSpec, ChartStyle) — bar/line/area/pie/donut specs (ChartSpec.bar(), LineInterpolation.MONOTONE, SliceLabelMode) compiled into engine primitives, no chart-specific render code.", - "tags": ["charts", "charts", "data-viz", "v1.9"], + "tags": ["charts", "data-viz", "v1.9"], "pdf": "showcase/pdf/features/charts/chart-showcase.pdf", "screenshot": "showcase/screenshots/features/charts/chart-showcase.png", "code": "https://github.com/DemchaAV/GraphCompose/blob/v2.0.0/examples/src/main/java/com/demcha/examples/features/charts/ChartShowcaseExample.java" @@ -690,7 +690,7 @@ "id": "debug-overlay", "title": "Debug Overlay", "description": "DocumentDebugOptions — guide lines plus semantic node-path labels on the rendered sheet; trace any misplaced block back to the builder call that authored it.", - "tags": ["debug", "debug", "labels", "v1.8"], + "tags": ["debug", "labels", "v1.8"], "pdf": "showcase/pdf/features/debug/debug-overlay.pdf", "screenshot": "showcase/screenshots/features/debug/debug-overlay.png", "code": "https://github.com/DemchaAV/GraphCompose/blob/v2.0.0/examples/src/main/java/com/demcha/examples/features/debug/DebugOverlayExample.java" @@ -705,7 +705,7 @@ "id": "word-export-companion", "title": "Word Export (DOCX)", "description": "DocxSemanticBackend — the same document as a fixed-layout PDF and an editable Word file; charts fall back to their data table.", - "tags": ["docx", "docx", "word", "export"], + "tags": ["docx", "word", "export"], "pdf": "showcase/pdf/features/docx/word-export-companion.pdf", "screenshot": "showcase/screenshots/features/docx/word-export-companion.png", "code": "https://github.com/DemchaAV/GraphCompose/blob/v2.0.0/examples/src/main/java/com/demcha/examples/features/docx/WordExportExample.java" @@ -720,7 +720,7 @@ "id": "block-align", "title": "Block Alignment", "description": "addAligned(align, node) / addSvgIcon(icon, w, align) — seat any fixed-size node left / centre / right across the content width.", - "tags": ["layout", "layout", "align", "v1.8"], + "tags": ["layout", "align", "v1.8"], "pdf": "showcase/pdf/features/layout/block-align.pdf", "screenshot": "showcase/screenshots/features/layout/block-align.png", "code": "https://github.com/DemchaAV/GraphCompose/blob/v2.0.0/examples/src/main/java/com/demcha/examples/features/layout/BlockAlignExample.java" @@ -729,7 +729,7 @@ "id": "content-bleed", "title": "Content Bleed", "description": "section.bleedToEdge(TOP, LEFT, RIGHT) and bleed(DocumentBleed.of(...)) push a fill to the trimmed page edge while children stay in the content margin.", - "tags": ["layout", "layout", "v1.9", "bleed"], + "tags": ["layout", "v1.9", "bleed"], "pdf": "showcase/pdf/features/layout/content-bleed.pdf", "screenshot": "showcase/screenshots/features/layout/content-bleed.png", "code": "https://github.com/DemchaAV/GraphCompose/blob/v2.0.0/examples/src/main/java/com/demcha/examples/features/layout/BleedExample.java" @@ -738,7 +738,7 @@ "id": "per-page-margin", "title": "Per-Page Margins", "description": "document.pageMargins(List.of(PageMarginRule.page(1, DocumentInsets.zero()), PageMarginRule.from(2, ...))) — a full-bleed cover then wide book margins in one session.", - "tags": ["layout", "layout", "v1.9"], + "tags": ["layout", "v1.9"], "pdf": "showcase/pdf/features/layout/per-page-margin.pdf", "screenshot": "showcase/screenshots/features/layout/per-page-margin.png", "code": "https://github.com/DemchaAV/GraphCompose/blob/v2.0.0/examples/src/main/java/com/demcha/examples/features/layout/PerPageMarginExample.java" @@ -747,7 +747,7 @@ "id": "row-columns", "title": "Row Columns", "description": "columns(auto(), weight(1), auto()) sizes row cells as intrinsic or weighted shares; paired with line().fill() a dotted leader fills the gap for a measureless table-of-contents row.", - "tags": ["layout", "layout", "v1.9", "rows"], + "tags": ["layout", "v1.9", "rows"], "pdf": "showcase/pdf/features/layout/row-columns.pdf", "screenshot": "showcase/screenshots/features/layout/row-columns.png", "code": "https://github.com/DemchaAV/GraphCompose/blob/v2.0.0/examples/src/main/java/com/demcha/examples/features/layout/RowColumnsExample.java" @@ -756,7 +756,7 @@ "id": "row-flex", "title": "Row Flex & Arrangement", "description": "RowBuilder.pushRight()/flexSpacer() springs absorb leftover row width while arrangement(RowArrangement.SPACE_BETWEEN/CENTER) justifies content-sized children — no manual coordinates.", - "tags": ["layout", "layout", "v1.9", "row"], + "tags": ["layout", "v1.9", "row"], "pdf": "showcase/pdf/features/layout/row-flex.pdf", "screenshot": "showcase/screenshots/features/layout/row-flex.png", "code": "https://github.com/DemchaAV/GraphCompose/blob/v2.0.0/examples/src/main/java/com/demcha/examples/features/layout/RowFlexExample.java" @@ -765,7 +765,7 @@ "id": "row-vertical-align", "title": "Row Vertical Align", "description": "RowBuilder.verticalAlign(RowVerticalAlign) — seats a row's children TOP, CENTER, or BOTTOM within the band set by the tallest child, no manual coordinates.", - "tags": ["layout", "layout", "v1.9"], + "tags": ["layout", "v1.9"], "pdf": "showcase/pdf/features/layout/row-vertical-align.pdf", "screenshot": "showcase/screenshots/features/layout/row-vertical-align.png", "code": "https://github.com/DemchaAV/GraphCompose/blob/v2.0.0/examples/src/main/java/com/demcha/examples/features/layout/RowVerticalAlignExample.java" @@ -780,7 +780,7 @@ "id": "container-bookmark", "title": "PDF Outline Bookmarks", "description": "bookmark(new DocumentBookmarkOptions(title)) on a container flow — each section becomes a PDF outline entry targeting its start page, building a navigable viewer panel with no manual coordinates.", - "tags": ["navigation", "navigation", "structure", "v1.9"], + "tags": ["navigation", "structure", "v1.9"], "pdf": "showcase/pdf/features/navigation/container-bookmark.pdf", "screenshot": "showcase/screenshots/features/navigation/container-bookmark.png", "code": "https://github.com/DemchaAV/GraphCompose/blob/v2.0.0/examples/src/main/java/com/demcha/examples/features/navigation/ContainerBookmarkExample.java" @@ -789,7 +789,7 @@ "id": "in-pdf-navigation", "title": "In-PDF Navigation", "description": "anchor(...) plus linkTo(...) — named destinations and internal go-to links for a clickable table of contents and bidirectional footnotes, resolved in a deferred pass for forward references.", - "tags": ["navigation", "navigation", "v1.9", "links"], + "tags": ["navigation", "v1.9", "links"], "pdf": "showcase/pdf/features/navigation/in-pdf-navigation.pdf", "screenshot": "showcase/screenshots/features/navigation/in-pdf-navigation.png", "code": "https://github.com/DemchaAV/GraphCompose/blob/v2.0.0/examples/src/main/java/com/demcha/examples/features/navigation/InPdfNavigationExample.java" @@ -798,7 +798,7 @@ "id": "page-reference", "title": "Page References", "description": "addPageReference(anchor, style, align) — prints the resolved page an anchor(...) lands on for \"see page N\" cross-refs, in one authoring pass via a second layout pass.", - "tags": ["navigation", "navigation", "v1.9", "structure"], + "tags": ["navigation", "v1.9", "structure"], "pdf": "showcase/pdf/features/navigation/page-reference.pdf", "screenshot": "showcase/screenshots/features/navigation/page-reference.png", "code": "https://github.com/DemchaAV/GraphCompose/blob/v2.0.0/examples/src/main/java/com/demcha/examples/features/navigation/PageReferenceExample.java" @@ -807,7 +807,7 @@ "id": "table-of-contents", "title": "Table of Contents", "description": "page.addTableOfContents(...) with entry(label, anchor) — clickable rows, a DocumentLeader.DOTS leader, and page numbers resolved automatically from the laid-out document.", - "tags": ["navigation", "navigation", "structure", "v1.9"], + "tags": ["navigation", "structure", "v1.9"], "pdf": "showcase/pdf/features/navigation/table-of-contents.pdf", "screenshot": "showcase/screenshots/features/navigation/table-of-contents.png", "code": "https://github.com/DemchaAV/GraphCompose/blob/v2.0.0/examples/src/main/java/com/demcha/examples/features/navigation/TocExample.java" @@ -822,7 +822,7 @@ "id": "multi-section-document", "title": "Multi-Section Document", "description": "GraphCompose.documents(out).section(cover).section(body) — concatenate independent DocumentSessions, each with its own page size and chrome, into one PDF with no external merge.", - "tags": ["structure", "structure", "v1.9"], + "tags": ["structure", "v1.9"], "pdf": "showcase/pdf/features/structure/multi-section-document.pdf", "screenshot": "showcase/screenshots/features/structure/multi-section-document.png", "code": "https://github.com/DemchaAV/GraphCompose/blob/v2.0.0/examples/src/main/java/com/demcha/examples/features/structure/MultiSectionExample.java" @@ -837,7 +837,7 @@ "id": "svg-icon-gallery", "title": "SVG Icon Gallery", "description": "34 real-world multicolour svgrepo icons through SvgIcon.parse — native vector layers, the whole set 156 KB of sources.", - "tags": ["svg", "svg", "icons", "v1.8"], + "tags": ["svg", "icons", "v1.8"], "pdf": "showcase/pdf/features/svg/svg-icon-gallery.pdf", "screenshot": "showcase/screenshots/features/svg/svg-icon-gallery.png", "code": "https://github.com/DemchaAV/GraphCompose/blob/v2.0.0/examples/src/main/java/com/demcha/examples/features/svg/SvgIconGalleryExample.java" @@ -884,6 +884,7 @@ "description": "Flagship cover page with hero panel, KPI table, and accent strip — ready-to-ship template.", "tags": ["flagship", "showcase", "cover"], "pdf": "showcase/pdf/flagships/default/business-report.pdf", + "pptx": "showcase/pptx/flagships/default/business-report.pptx", "screenshot": "showcase/screenshots/flagships/default/business-report.png", "code": "https://github.com/DemchaAV/GraphCompose/blob/v2.0.0/examples/src/main/java/com/demcha/examples/flagships/BusinessReportExample.java" }, @@ -891,25 +892,17 @@ "id": "engine-deck", "title": "Engine Deck", "description": "Landscape flagship deck — hero banner, SVG-icon feature spreads, and benchmark tables and charts the engine renders from comparative data.", - "tags": ["flagship", "showcase", "flagship"], + "tags": ["flagship", "showcase"], "pdf": "showcase/pdf/flagships/default/engine-deck.pdf", + "pptx": "showcase/pptx/flagships/default/engine-deck.pptx", "screenshot": "showcase/screenshots/flagships/default/engine-deck.png", "code": "https://github.com/DemchaAV/GraphCompose/blob/v2.0.0/examples/src/main/java/com/demcha/examples/flagships/EngineDeckExample.java" }, - { - "id": "engine-deck-v2", - "title": "Engine Deck V2", - "description": "Generated showcase for flagships / default.", - "tags": ["flagships", "default"], - "pdf": "showcase/pdf/flagships/default/engine-deck-v2.pdf", - "screenshot": "showcase/screenshots/flagships/default/engine-deck-v2.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/v2.0.0/examples/src/main/java/com/demcha/examples" - }, { "id": "feature-catalog", "title": "Feature Catalog", "description": "A guided catalog of the engine's primitives, one section per capability, every heading registered as a PDF outline bookmark for a navigable index.", - "tags": ["flagship", "showcase", "flagship"], + "tags": ["flagship", "showcase"], "pdf": "showcase/pdf/flagships/default/feature-catalog.pdf", "screenshot": "showcase/screenshots/flagships/default/feature-catalog.png", "code": "https://github.com/DemchaAV/GraphCompose/blob/v2.0.0/examples/src/main/java/com/demcha/examples/flagships/FeatureCatalogExample.java" @@ -918,8 +911,9 @@ "id": "financial-report", "title": "Financial Report", "description": "A polished financial-report flagship — clipped-photo masthead, KPI tables, and vector charts combining the engine's data-viz and shape primitives.", - "tags": ["flagship", "showcase", "flagship"], + "tags": ["flagship", "showcase"], "pdf": "showcase/pdf/flagships/default/financial-report.pdf", + "pptx": "showcase/pptx/flagships/default/financial-report.pptx", "screenshot": "showcase/screenshots/flagships/default/financial-report.png", "code": "https://github.com/DemchaAV/GraphCompose/blob/v2.0.0/examples/src/main/java/com/demcha/examples/flagships/FinancialReportExample.java" }, @@ -929,6 +923,7 @@ "description": "Kitchen-sink demo combining every primitive into a single document — the full GraphCompose surface.", "tags": ["flagship", "showcase"], "pdf": "showcase/pdf/flagships/default/master-showcase.pdf", + "pptx": "showcase/pptx/flagships/default/master-showcase.pptx", "screenshot": "showcase/screenshots/flagships/default/master-showcase.png", "code": "https://github.com/DemchaAV/GraphCompose/blob/v2.0.0/examples/src/main/java/com/demcha/examples/flagships/MasterShowcaseExample.java" }, @@ -940,6 +935,16 @@ "pdf": "showcase/pdf/flagships/default/module-first-profile.pdf", "screenshot": "showcase/screenshots/flagships/default/module-first-profile.png", "code": "https://github.com/DemchaAV/GraphCompose/blob/v2.0.0/examples/src/main/java/com/demcha/examples/flagships/ModuleFirstFileExample.java" + }, + { + "id": "twin-output", + "title": "Twin Output", + "description": "One 16:9 page written once and emitted twice from the same session — a print-ready PDF and a PowerPoint slide with identical geometry where text, panels, and vectors stay native, editable shapes.", + "tags": ["flagship", "showcase"], + "pdf": "showcase/pdf/flagships/default/twin-output.pdf", + "pptx": "showcase/pptx/flagships/default/twin-output.pptx", + "screenshot": "showcase/screenshots/flagships/default/twin-output.png", + "code": "https://github.com/DemchaAV/GraphCompose/blob/v2.0.0/examples/src/main/java/com/demcha/examples/flagships/TwinOutputExample.java" } ] } diff --git a/web/showcase/pdf/flagships/default/twin-output.pdf b/web/showcase/pdf/flagships/default/twin-output.pdf new file mode 100644 index 00000000..54788c69 Binary files /dev/null and b/web/showcase/pdf/flagships/default/twin-output.pdf differ diff --git a/web/showcase/pptx/flagships/default/business-report.pptx b/web/showcase/pptx/flagships/default/business-report.pptx new file mode 100644 index 00000000..527d2bae Binary files /dev/null and b/web/showcase/pptx/flagships/default/business-report.pptx differ diff --git a/web/showcase/pptx/flagships/default/engine-deck.pptx b/web/showcase/pptx/flagships/default/engine-deck.pptx new file mode 100644 index 00000000..3318b562 Binary files /dev/null and b/web/showcase/pptx/flagships/default/engine-deck.pptx differ diff --git a/web/showcase/pptx/flagships/default/financial-report.pptx b/web/showcase/pptx/flagships/default/financial-report.pptx new file mode 100644 index 00000000..d1ec4291 Binary files /dev/null and b/web/showcase/pptx/flagships/default/financial-report.pptx differ diff --git a/web/showcase/pptx/flagships/default/master-showcase.pptx b/web/showcase/pptx/flagships/default/master-showcase.pptx new file mode 100644 index 00000000..17895b07 Binary files /dev/null and b/web/showcase/pptx/flagships/default/master-showcase.pptx differ diff --git a/web/showcase/pptx/flagships/default/twin-output.pptx b/web/showcase/pptx/flagships/default/twin-output.pptx new file mode 100644 index 00000000..5d64e18a Binary files /dev/null and b/web/showcase/pptx/flagships/default/twin-output.pptx differ diff --git a/web/showcase/screenshots/flagships/default/twin-output.png b/web/showcase/screenshots/flagships/default/twin-output.png new file mode 100644 index 00000000..c6e45ade Binary files /dev/null and b/web/showcase/screenshots/flagships/default/twin-output.png differ