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
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,27 @@ follow semantic versioning; release dates are ISO 8601.

### Documentation

- **The examples stop describing the releases they were written for.** Eight committed
previews read as documents about 1.x: three framed a current feature as "v1.6 Phase
A/B/C" — a plan for a release that shipped — one told the reader to tag v1.9.0 to
publish a module that has been on Central since, two signed off "Composed with
GraphCompose v1.5", one badged a canvas demo "v1.8", and the certificate on the
free-canvas page was awarded for shipping v1.6. They describe what they demonstrate
now, so nothing in them dates again. The hyperlink example was worse than dated: both
URLs it rendered — a template-authoring page and a v1.6 roadmap — had been deleted, so
the example that demonstrates links shipped two of them broken. They point at the
preset cheatsheet and the extension guide.
- **A document that prints the date can be rendered twice and come out the same.** The
`{date}` header token resolved from the clock with no way to pin it, so any document
using it was a different file every morning — and the example demonstrating it could
not be held to its committed preview at all. `-Dgraphcompose.renderDate=YYYY-MM-DD`
fixes what the token resolves to, the way `SOURCE_DATE_EPOCH` does for archives; unset,
it is the clock as before. The examples module pins it, so that preview is now compared
like every other one instead of being trusted.
- **Two things the previews used to be read for are now checked.** A preview naming a
release the project has moved past, and an example rendering a link to a repository
path that no longer exists — both render perfectly, so only reading caught them, and
both had been true for six releases.
- **A committed preview can be reproduced from a branch that has moved past it.** The
documents that print a version took it from the reactor, which between releases sits on
the next patch — so a render from `develop` named a version nobody could depend on yet,
Expand Down
Binary file modified assets/readme/examples/canvas-layer-showcase.pdf
Binary file not shown.
Binary file modified assets/readme/examples/composed-table-cell-showcase.pdf
Binary file not shown.
Binary file modified assets/readme/examples/cover-letter.pdf
Binary file not shown.
Binary file modified assets/readme/examples/feature-catalog.pdf
Binary file not shown.
Binary file modified assets/readme/examples/inline-highlight-chips.pdf
Binary file not shown.
Binary file modified assets/readme/examples/nested-list-showcase.pdf
Binary file not shown.
Binary file modified assets/readme/examples/pdf-chrome.pdf
Binary file not shown.
Binary file modified assets/readme/examples/rich-text-showcase.pdf
Binary file not shown.
Binary file modified assets/readme/examples/weekly-schedule.pdf
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,34 @@ public String resolveTokens(String text, int physicalPage, int totalPages) {
return text
.replace("{page}", numberStyle.format(counted))
.replace("{pages}", numberStyle.format(countedTotal))
.replace("{date}", java.time.LocalDate.now().toString());
.replace("{date}", renderDate().toString());
}

/**
* The date the {@code {date}} token resolves to.
*
* <p>The clock by default. A build that has to produce the same bytes twice can pin it with
* {@code -Dgraphcompose.renderDate=YYYY-MM-DD} — the same need {@code SOURCE_DATE_EPOCH}
* answers for archives, and the reason this repository can hold its committed example
* previews to a byte comparison: a document that prints today re-renders differently every
* morning, and a guard that reports that is a guard people learn to ignore.</p>
*
* <p>An unparseable value is the clock again rather than a failed render: a mistyped property
* should not stop a document being produced, and the drift it causes surfaces where drift is
* checked.</p>
*
* @return the pinned date, or today
*/
static java.time.LocalDate renderDate() {
String pinned = System.getProperty("graphcompose.renderDate");
if (pinned == null || pinned.isBlank()) {
return java.time.LocalDate.now();
}
try {
return java.time.LocalDate.parse(pinned.trim());
} catch (java.time.format.DateTimeParseException notADate) {
return java.time.LocalDate.now();
}
}

/**
Expand Down Expand Up @@ -129,6 +156,6 @@ public static String resolvePlaceholders(String text, int currentPage, int total
return text
.replace("{page}", String.valueOf(currentPage))
.replace("{pages}", String.valueOf(totalPages))
.replace("{date}", java.time.LocalDate.now().toString());
.replace("{date}", renderDate().toString());
}
}
31 changes: 31 additions & 0 deletions examples/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@
-->
<graphcompose.examples.assetVersion>2.1.0</graphcompose.examples.assetVersion>

<!--
The date the committed previews print where an example demonstrates the
{date} token. Pinned rather than "today" so a preview is a function of the
code that renders it; the token itself still resolves from the clock for
everybody else. GenerateAllExamples passes it below, so the release renders
the same date the drift gate compares against.
-->
<graphcompose.examples.renderDate>2026-01-15</graphcompose.examples.renderDate>

<junit.bom.version>6.1.2</junit.bom.version>
<assertj.version>3.27.7</assertj.version>
</properties>
Expand Down Expand Up @@ -202,6 +211,14 @@
<graphcompose.examples.displayVersion>
${graphcompose.examples.assetVersion}
</graphcompose.examples.displayVersion>
<!--
And render at the date the previews carry. The chrome example
prints the {date} token, which the engine resolves from the clock,
so without this its committed preview differs from a fresh render
every day but the one it was committed on — a guard going red each
morning on a tree nobody touched.
-->
<graphcompose.renderDate>${graphcompose.examples.renderDate}</graphcompose.renderDate>
</systemPropertyVariables>
</configuration>
</plugin>
Expand All @@ -210,6 +227,20 @@
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.6.3</version>
<configuration>
<!--
GenerateAllExamples renders the committed previews, so it has to
pin the same date the tests compare them at. Set here rather than
on the command line: the release script invokes exec:java, and a
flag it forgets is a preview that drifts by a day.
-->
<systemProperties>
<systemProperty>
<key>graphcompose.renderDate</key>
<value>${graphcompose.examples.renderDate}</value>
</systemProperty>
</systemProperties>
</configuration>
</plugin>
</plugins>
</build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import java.nio.file.Path;

/**
* Runnable showcase for the v1.6 Phase C
* Runnable showcase for the
* {@link com.demcha.compose.document.node.CanvasLayerNode} —
* places child nodes at explicit {@code (x, y)} coordinates
* inside a fixed-size bounding box. The generated PDF is a
Expand Down Expand Up @@ -108,7 +108,7 @@ public static Path generate() throws Exception {
document.pageFlow()
.name("CanvasShowcase")
.spacing(8)
.addParagraph("v1.6 Phase C — CanvasLayerNode (controlled free-canvas)", title)
.addParagraph("CanvasLayerNode — controlled free-canvas", title)
.addParagraph(
"Every element below is placed at an explicit (x, y) inside the canvas's "
+ "523 x 360 bounding box. Coordinates use the screen convention: "
Expand Down Expand Up @@ -138,7 +138,7 @@ public static Path generate() throws Exception {
DocumentInsets.zero(), DocumentInsets.zero()),
0, 60)
.position(new ParagraphNode(
"Headline", "GraphCompose v1.6",
"Headline", "GraphCompose",
headline, TextAlign.CENTER, 0.0,
DocumentInsets.zero(), DocumentInsets.zero()),
0, 95)
Expand All @@ -158,10 +158,10 @@ public static Path generate() throws Exception {
// each side.
.position(new ParagraphNode(
"Citation",
"Issued for shipping the v1.6 expressive release "
+ "with Templates v2, nested lists, composed "
+ "table cells, and pixel-precise free-canvas "
+ "layout in a single iteration.",
"Issued for a page composed on a free canvas: "
+ "every line placed at an exact point, wrapped "
+ "to a width the layout was told rather than "
+ "left to infer.",
bodyText, TextAlign.CENTER, 2.0,
DocumentInsets.zero(),
new DocumentInsets(0, 80, 0, 0)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import java.nio.file.Path;

/**
* Runnable showcase for the v1.6 Phase A nested-list ergonomics:
* Runnable showcase for the nested-list ergonomics:
* {@code ListBuilder.addItem(label, Consumer)}, {@code markerFor(depth)}
* overrides, mixed flat / nested authoring, and the built-in marker
* cascade ({@code •} → {@code ◦} → {@code ▪} → {@code ·}). Each section
Expand Down Expand Up @@ -66,7 +66,7 @@ public static Path generate() throws Exception {
document.pageFlow()
.name("NestedListShowcase")
.spacing(8)
.addParagraph("v1.6 Phase A — Nested list ergonomics", title)
.addParagraph("Nested list ergonomics", title)
.addParagraph(
"ListBuilder.addItem(label, body) appends a list item with a builder "
+ "callback that scopes children. Per-depth markers, source-order "
Expand All @@ -83,15 +83,15 @@ public static Path generate() throws Exception {
.markerFor(2, ListMarker.custom("*"))
.addItem("Engineering Roadmap", q1 -> q1
.addItem("Document Engine", phaseA -> phaseA
.addItem("Nested lists landed in v1.6")
.addItem("Composed table cells landed in v1.6")
.addItem("Nested lists with a per-depth marker cascade")
.addItem("Table cells that compose a whole flow")
.addItem("Templates v2 with visual parity gate"))
.addItem("Backend SPI", phaseB -> phaseB
.addItem("PdfFragmentRenderHandler is now public")
.addItem("DOCX semantic backend skeleton")))
.addItem("Documentation",
docs -> docs
.addItem("Migration guide v1.5 to v1.6")
.addItem("Migration guide for the layered presets")
.addItem("ADRs 0011-0013 published")))

// 2) markerFor() per-depth override + per-item marker.
Expand Down Expand Up @@ -130,7 +130,7 @@ public static Path generate() throws Exception {
.addItem("Ran mvnw verify locally"))
.addItem("Closed bug: marker double-space rendering")
.addItem("Triaged backlog", triage -> triage
.addItem("Phase E.4 deferred to v1.7")
.addItem("Deferred: hanging indent on wrapped items")
.addItem("CanvasLayerNode parked")))

// 4) Deep nesting (depth 4+) falls back to the · cascade.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
import java.nio.file.Path;

/**
* Runnable showcase for the v1.6 Phase B composed table cell content:
* Runnable showcase for composed table cell content:
* {@code DocumentTableCell.node(DocumentNode)} accepts any composable
* canonical node and the table layout pipeline prepares the child
* sub-tree against the cell's resolved inner width, sizes the row from
Expand All @@ -33,8 +33,8 @@
*
* <p>The generated PDF puts paragraphs (with markdown rich text) and a
* nested list inside table cells, alongside plain-text cells, so the
* difference between the v1.5 line-only shape and the v1.6 composed
* shape is visible at a glance.</p>
* difference between a line-only cell and a composed one is visible
* at a glance.</p>
*
* @author Artem Demchyshyn
*/
Expand Down Expand Up @@ -109,7 +109,7 @@ public static Path generate() throws Exception {
document.pageFlow()
.name("ComposedCellShowcase")
.spacing(8)
.addParagraph("v1.6 Phase B — Composed table cell content", title)
.addParagraph("Composed table cell content", title)
.addParagraph(
"DocumentTableCell.node(DocumentNode) accepts any registered "
+ "canonical node — paragraphs (with markdown), nested lists, "
Expand Down Expand Up @@ -223,7 +223,7 @@ public static Path generate() throws Exception {
.addParagraph("3. Mixed composed and plain-text cells in the same row", sectionHeading)
.addParagraph(
"Plain-text cells continue to use the existing DocumentTableCell.text(...) "
+ "factory and render through the v1.5 line-iteration code path. "
+ "factory and render through the line-iteration code path. "
+ "Composed cells render via NodeDefinition recursion alongside.", caption)
.build();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ public static Path generate() throws Exception {
.addSection("Code", section -> labelledRow(section,
"code(text) — monospace on a light chip, engine defaults",
rich -> rich
.plain("Run ").code("./mvnw verify").plain(" then tag ")
.code("v1.9.0").plain(" to publish ").code("graph-compose-emoji")))
.plain("Run ").code("./mvnw verify").plain(" before pushing, and ")
.code("-Dtest=Name").plain(" to narrow it to one class")))
.addSection("Badges", section -> labelledRow(section,
"chip(text, fg, bg) — a coloured status badge between words",
rich -> rich
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,11 +115,11 @@ public static Path generate() throws Exception {
"link",
rich -> rich
.plain("Read the ")
.link("template authoring cheatsheet",
"https://github.com/DemchaAV/GraphCompose/blob/develop/docs/template-authoring.md")
.link("preset authoring cheatsheet",
"https://github.com/DemchaAV/GraphCompose/blob/develop/docs/templates/v2-layered/authoring-presets.md")
.plain(" or the ")
.link("v1.6 roadmap",
"https://github.com/DemchaAV/GraphCompose/blob/develop/docs/v1.6-roadmap.md")
.link("extension guide",
"https://github.com/DemchaAV/GraphCompose/blob/develop/docs/contributing/extension-guide.md")
.plain(" for the next steps.")))
.addSection("Composing runs", section -> labelledRow(section,
"append",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -395,12 +395,12 @@ public static Path generate() throws Exception {

feature(flow, "Canvas — absolute (x, y) placement", """
section.addCanvas(220, 70, canvas -> canvas
.position(badge("v1.8"), 8, 8)
.position(badge("layout"), 8, 8)
.position(badge("charts"), 84, 26)
.position(badge("paint"), 160, 8))""",
demo -> demo.addCanvas(220, 70, canvas -> canvas
.clipPolicy(ClipPolicy.OVERFLOW_VISIBLE)
.position(badge("v1.8"), 8, 8)
.position(badge("layout"), 8, 8)
.position(badge("charts"), 84, 26)
.position(badge("paint"), 160, 8)));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -537,7 +537,7 @@ public static void renderTo(Path outputFile,
.addSection("BuildFooter", section -> section
.padding(new DocumentInsets(6, 0, 0, 0))
.addParagraph(p -> p
.text("Composed with GraphCompose v1.5 — examples/.../WeeklyScheduleRenderer.java")
.text("Composed with GraphCompose — examples/.../WeeklyScheduleRenderer.java")
.textStyle(DocumentTextStyle.builder()
.fontName(FontName.COURIER)
.size(7.5)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
/**
* Modern cinematic cover letter rendered directly through the canonical
* DSL — `BusinessTheme.modern()` drives colour and type, sections use
* v1.5 presets ({@code softPanel}, {@code accentLeft}, {@code accentTop})
* presets ({@code softPanel}, {@code accentLeft}, {@code accentTop})
* for the visual hierarchy, and an opening rich-text strip highlights
* the candidate's headline value proposition.
*/
Expand Down Expand Up @@ -214,7 +214,7 @@ public static Path generate() throws Exception {
.accentTop(THEME.palette().rule(), 0.6)
.padding(new DocumentInsets(8, 0, 0, 0))
.addRich(rich -> rich
.plain("Composed with GraphCompose v1.5 — ")
.plain("Composed with GraphCompose — ")
.style("examples/.../CoverLetterFileExample.java", DocumentTextStyle.builder()
.fontName(FontName.COURIER)
.size(8)
Expand Down
Loading
Loading