Skip to content
Merged
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
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ jobs:
- '**/pom.xml'
- '**/src/main/resources/**'
- '**/src/test/resources/**'
# The examples module renders figures from the committed perf
# baseline, so a baseline-only refresh changes generated output.
- 'baselines/**'
- '.mvn/**'
- 'mvnw'
- 'mvnw.cmd'
Expand Down
Binary file modified assets/readme/examples/master-showcase.pdf
Binary file not shown.
Binary file modified assets/readme/examples/master-showcase.pptx
Binary file not shown.
14 changes: 14 additions & 0 deletions examples/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,20 @@
<exclude>banner.properties</exclude>
</excludes>
</resource>
<!--
The committed perf baseline, read straight from where the gate
keeps it. Examples that quote engine speed restate this file
rather than carrying literals, and pointing at the original
instead of copying it is what keeps the two from drifting.
-->
<resource>
<directory>${project.basedir}/../baselines</directory>
<targetPath>baselines</targetPath>
<filtering>false</filtering>
<includes>
<include>current-speed-full.json</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,16 @@
import com.demcha.compose.document.svg.SvgIcon;
import com.demcha.compose.font.FontName;
import com.demcha.examples.support.ExampleOutputPaths;
import com.demcha.examples.support.ExampleVersion;

import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* Parallel 2.0 release concept for the GraphCompose README hero and engine
Expand Down Expand Up @@ -116,7 +113,7 @@ public final class EngineDeckV2Example {
private static final DocumentColor PALE_BLUE = DocumentColor.rgb(232, 243, 255);
private static final DocumentColor PALE_MINT = DocumentColor.rgb(230, 249, 242);

private static final String VERSION = loadVersion();
private static final String VERSION = ExampleVersion.current();

/**
* The {@code major.minor} of {@link #VERSION}, for the banner's prose labels.
Expand All @@ -125,7 +122,7 @@ public final class EngineDeckV2Example {
* so a literal would keep announcing whichever line it was typed on — the hero
* still read "2.0" while the pill beside it read v2.1.0.</p>
*/
private static final String VERSION_LINE = majorMinor(VERSION);
private static final String VERSION_LINE = ExampleVersion.majorMinor(VERSION);

private EngineDeckV2Example() {
}
Expand Down Expand Up @@ -912,31 +909,6 @@ private static String snapshotDate(EngineDeckData.BenchRun bench) {
return timestamp.substring(0, Math.min(10, timestamp.length()));
}

/**
* Reduces a version to its {@code major.minor} line for display.
*
* @param version the resolved build version, possibly a qualifier or {@code "dev"}
* @return {@code "2.1"} for {@code "2.1.0"} / {@code "2.1.0-SNAPSHOT"}; the input
* unchanged when it carries no dotted numeric prefix
*/
private static String majorMinor(String version) {
Matcher matcher = Pattern.compile("^(\\d+)\\.(\\d+)").matcher(version);
return matcher.find() ? matcher.group(1) + "." + matcher.group(2) : version;
}

private static String loadVersion() {
Properties banner = new Properties();
try (InputStream in = EngineDeckV2Example.class.getResourceAsStream("/banner.properties")) {
if (in != null) {
banner.load(in);
}
} catch (IOException ignored) {
// Fall through to the development label below.
}
String value = banner.getProperty("version");
return value == null || value.isBlank() || value.startsWith("@") ? "dev" : value.trim();
}

private static SvgIcon logo() {
return icon("logo");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,11 @@
import com.demcha.examples.support.theme.BusinessTheme;
import com.demcha.compose.font.FontName;
import com.demcha.examples.support.ExampleOutputPaths;
import com.demcha.examples.support.ExampleVersion;
import com.demcha.examples.support.PerfBaseline;

import java.nio.file.Path;
import java.util.Locale;

/**
* Kitchen-sink showcase combining the canonical surface end-to-end —
Expand All @@ -34,10 +37,17 @@
* QR + Code 128 barcodes, document metadata, and header / footer chrome.
*
* <p>The rendered PDF reads like a fictional "GraphCompose Q2 Sample
* Report" — three pages of designed content meant to look at a glance
* Report" — designed content meant to look at a glance
* like the kind of business document GraphCompose was built to
* generate, not a feature checklist. Use it as a reference when
* composing your own multi-page documents.</p>
*
* <p>The narrative is deliberately undated. This file is regenerated and
* re-committed on every release, so anything that names a version, a date or a
* measurement would go stale between one release and the next — as it did. What
* survives is either sample copy that cannot age or a figure read at render time
* from {@link com.demcha.examples.support.PerfBaseline} and
* {@link com.demcha.examples.support.ExampleVersion}.</p>
*/
public final class MasterShowcaseExample {
private static final BusinessTheme THEME = BusinessTheme.modern();
Expand Down Expand Up @@ -90,7 +100,7 @@ static void compose(DocumentSession document) {
document.metadata(DocumentMetadata.builder()
.title("GraphCompose master showcase")
.author("Jordan Rivera")
.subject("Comprehensive end-to-end demo of the v1.5 canonical surface")
.subject("Comprehensive end-to-end demo of the canonical document surface")
.keywords("graphcompose, showcase, business, theme, rich-text, table, shape, transform, barcode")
.creator("GraphCompose Examples")
.producer("GraphCompose")
Expand All @@ -109,7 +119,7 @@ static void compose(DocumentSession document) {

document.footer(DocumentHeaderFooter.builder()
.zone(DocumentHeaderFooterZone.FOOTER)
.leftText("v1.5 — \"intuitive\" release")
.leftText("GraphCompose " + ExampleVersion.currentLine() + " — sample report")
.rightText("Page {page} of {pages}")
.fontSize(9f)
.textColor(MUTED)
Expand Down Expand Up @@ -165,16 +175,17 @@ static void compose(DocumentSession document) {
.plain("Status: ")
.bold("On track")
.plain(" — ")
.accent("675 / 675 tests green", BRAND)
.plain(" — Q2 release window confirmed for ")
.underline("June 2026")
.accent("every gate green", BRAND)
.plain(" — release window confirmed for the ")
.underline("current quarter")
.plain("."))
.addRich(rich -> rich
.plain("Performance: ")
.color("invoice-template", BRAND_DEEP)
.plain(" 13.4 ms avg, 75 docs/sec; ")
.plain(" " + speed("invoice-template") + "; ")
.color("feature-rich", BRAND_DEEP)
.plain(" 36.8 ms avg, 27 docs/sec.")))
.plain(" " + speed("feature-rich") + ". One machine, "
+ PerfBaseline.get().capturedOn() + ".")))
.addSection("Seal", section -> section
.padding(DocumentInsets.of(2))
.addCircle(118, BRAND, circle -> circle
Expand All @@ -187,7 +198,7 @@ static void compose(DocumentSession document) {
style(FontName.HELVETICA_BOLD, 13,
DocumentTextDecoration.BOLD,
DocumentColor.WHITE)))
.position(label("Q2 / 2026",
.position(label("SAMPLE",
style(FontName.HELVETICA_BOLD, 7.5,
DocumentTextDecoration.BOLD,
SOFT_GOLD)),
Expand Down Expand Up @@ -222,7 +233,7 @@ static void compose(DocumentSession document) {
.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: ")
.plain("The features below are what turn GraphCompose from a \"tidy PDF layouter\" into a designed-document engine: ")
.bold("shape-as-container with clip path")
.plain(", ")
.bold("rotate / scale + per-layer z-index")
Expand Down Expand Up @@ -314,7 +325,7 @@ static void compose(DocumentSession document) {
.addSection("Card3", section -> highlightCard(section,
"Transformable mixin",
"rotate / scale on every shape",
"v1.5 extends Transformable<T> to every leaf builder so any shape rotates around its placement centre.")))
"Transformable<T> reaches every leaf builder, so any shape rotates around its placement centre.")))

// ───── Action items + status legend ─────
.addSection("ActionItems", section -> section
Expand All @@ -328,9 +339,9 @@ static void compose(DocumentSession document) {
.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. ")
.plain("• Publish the release notes once the branch ")
.bold("merges through review")
.plain(". ")
.accent("Owner: maintainer", BRAND)
.plain("."))
.addRich(rich -> rich
Expand Down Expand Up @@ -360,7 +371,7 @@ static void compose(DocumentSession document) {
.padding(DocumentInsets.zero())
.addBarcode(barcode -> barcode
.code128()
.data("GC-MASTER-Q2-2026")
.data("GC-MASTER-SAMPLE")
.foreground(BRAND)
.size(180, 36))))
.build();
Expand Down Expand Up @@ -403,6 +414,23 @@ private static DocumentNode label(String text, DocumentTextStyle style) {
.build();
}

/**
* One scenario's measured speed, phrased for the report.
*
* <p>Restates the committed baseline rather than a literal, so the figure
* moves when the measurement does instead of when somebody remembers.</p>
*
* @param scenario benchmark scenario name
* @return e.g. {@code "6.6 ms avg, 152 docs/sec"}, or a plain note when the
* baseline does not carry the scenario
*/
private static String speed(String scenario) {
return PerfBaseline.get().scenario(scenario)
.map(measured -> String.format(Locale.ROOT, "%.1f ms avg, %.0f docs/sec",
measured.avgMillis(), measured.docsPerSecond()))
.orElse("not measured");
}

private static DocumentTextStyle label() {
return DocumentTextStyle.builder()
.fontName(FontName.HELVETICA_BOLD)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package com.demcha.examples.support;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* The reactor version, for examples that print it.
*
* <p>Read from the filtered {@code banner.properties} rather than written out,
* because example documents are regenerated on every release: a literal keeps
* announcing whichever line it was typed on. Shared so the value cannot drift
* between the documents that show it.</p>
*/
public final class ExampleVersion {

private static final Pattern MAJOR_MINOR = Pattern.compile("^(\\d+)\\.(\\d+)");

private static final String CURRENT = load();

private ExampleVersion() {
}

/**
* The full reactor version, or {@code "dev"} when the resource is absent or
* unfiltered (running straight from sources).
*
* @return version string
*/
public static String current() {
return CURRENT;
}

/**
* The {@code major.minor} of {@link #current()}, for prose that names a line
* rather than a patch.
*
* @return the leading {@code major.minor}, or the version unchanged when it
* carries no dotted numeric prefix
*/
public static String currentLine() {
return majorMinor(CURRENT);
}

/**
* Reduces a version to its {@code major.minor} prefix.
*
* @param version version string to reduce
* @return the leading {@code major.minor}, or {@code version} unchanged when
* it carries no dotted numeric prefix
*/
public static String majorMinor(String version) {
Matcher matcher = MAJOR_MINOR.matcher(version);
return matcher.find() ? matcher.group(1) + "." + matcher.group(2) : version;
}

private static String load() {
Properties banner = new Properties();
try (InputStream in = ExampleVersion.class.getResourceAsStream("/banner.properties")) {
if (in != null) {
banner.load(in);
}
} catch (IOException ignored) {
// Fall through to the development label below.
}
String value = banner.getProperty("version");
return value == null || value.isBlank() || value.startsWith("@") ? "dev" : value.trim();
}
}
Loading
Loading