diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0eba9453..c12c0fd0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,10 +3,49 @@
All notable changes to GraphCompose are documented here. Versions
follow semantic versioning; release dates are ISO 8601.
-## v2.1.0 — in progress
+## v2.1.0 — Planned
### Public API
+- **Keep a heading with its content** — `SectionBuilder.keepWithNext()`. A section
+ marked keep-with-next is never left stranded as the last block on a page apart from
+ the content it introduces: when the section plus the first slice of the following
+ block would overflow the remaining page space (but fit on a fresh page), the section
+ relocates to the next page so the heading stays glued to its body. The first slice is
+ a paragraph's first line, a table's repeated header rows plus first body row, or a
+ list's first item, so the rule holds whether the following block is atomic or a
+ page-spanning table or list. Distinct from `keepTogether()`, which relocates a
+ *whole* block — keep-with-next binds only the start of the following block, the right
+ tool for a boxed section title above a long, page-spanning body. Inert when nothing
+ follows (a trailing heading is never moved) and best-effort when the heading plus the
+ first slice cannot share a page. Default off, so layouts that do not opt in are
+ unchanged.
+- `LineBuilder.keepWithNext()` — the line counterpart of
+ `SectionBuilder.keepWithNext()`, so a full-width header rule joins its banner's
+ keep-with-next run and the whole title block (rule + banner + rule) relocates
+ together instead of the banner stranding apart from its rules or its body.
+- Keep-with-next surfaces on the node model as well: `DocumentNode.keepWithNext()`
+ is a new default method (existing implementations keep the `false` default and
+ need no change), and `SectionNode` and `LineNode` each gain a trailing
+ `keepWithNext` record component. Constructor calls are unaffected — both records
+ keep an overload at the previous argument count that defaults the flag to
+ `false` — but the canonical component count changed (`SectionNode` 13 → 14,
+ `LineNode` 17 → 18), so a **record deconstruction pattern** written against the
+ 2.0.0 component list must add the new binding to compile.
+- **Single-column CV presets no longer orphan a section title.** Every preset whose
+ sections flow down the page — BoxedSections, MinimalUnderlined, ModernProfessional,
+ Executive, CenteredHeadline, BlueBanner, EditorialBlue, and ClassicSerif — keeps a
+ section heading with the first line of its body across a page break: a standalone
+ header section binds to the following body section (a multi-node rule + banner + rule
+ group binds as one run), and a combined header+body module keeps the heading in a
+ nested keep-with-next group — including Panel's full-width Profile card, whose header
+ now stays with the summary if a long profile splits the card. Multi-column presets
+ place their sections in fixed columns that do not paginate, so no heading can strand
+ there.
+- **The Modern Proposal template no longer orphans a section heading.** Its flowing
+ section bodies, the Timeline and Investment tables, and the Acceptance terms each keep
+ their title with the first line of the block it introduces across a page break — each
+ title now renders in its own keep-with-next section rather than a bare paragraph.
- **CV entry titles and subtitles accept inline `[text](url)` links.** In the layered
CV presets, a `[label](url)` in an experience/education entry title or subtitle now
renders as a clickable hyperlink — the same inline-Markdown convention already used
@@ -303,37 +342,6 @@ for this cycle.
### Public API
-- **Keep a heading with its content** — `SectionBuilder.keepWithNext()`. A section
- marked keep-with-next is never left stranded as the last block on a page apart from
- the content it introduces: when the section plus the first slice of the following
- block would overflow the remaining page space (but fit on a fresh page), the section
- relocates to the next page so the heading stays glued to its body. The first slice is
- a paragraph's first line, a table's repeated header rows plus first body row, or a
- list's first item, so the rule holds whether the following block is atomic or a
- page-spanning table or list. Distinct from `keepTogether()`, which relocates a
- *whole* block — keep-with-next binds only the start of the following block, the right
- tool for a boxed section title above a long, page-spanning body. Inert when nothing
- follows (a trailing heading is never moved) and best-effort when the heading plus the
- first slice cannot share a page. Default off, so layouts that do not opt in are
- unchanged.
-- `LineBuilder.keepWithNext()` — the line counterpart of
- `SectionBuilder.keepWithNext()`, so a full-width header rule joins its banner's
- keep-with-next run and the whole title block (rule + banner + rule) relocates
- together instead of the banner stranding apart from its rules or its body.
-- **Single-column CV presets no longer orphan a section title.** Every preset whose
- sections flow down the page — BoxedSections, MinimalUnderlined, ModernProfessional,
- Executive, CenteredHeadline, BlueBanner, EditorialBlue, and ClassicSerif — keeps a
- section heading with the first line of its body across a page break: a standalone
- header section binds to the following body section (a multi-node rule + banner + rule
- group binds as one run), and a combined header+body module keeps the heading in a
- nested keep-with-next group — including Panel's full-width Profile card, whose header
- now stays with the summary if a long profile splits the card. Multi-column presets
- place their sections in fixed columns that do not paginate, so no heading can strand
- there.
-- **The Modern Proposal template no longer orphans a section heading.** Its flowing
- section bodies, the Timeline and Investment tables, and the Acceptance terms each keep
- their title with the first line of the block it introduces across a page break — each
- title now renders in its own keep-with-next section rather than a bare paragraph.
- **Reproducible PDF output** (`@Beta`). `PdfFixedLayoutBackend.builder().deterministic(true)`
(or `.deterministic(Instant)` for an explicit timestamp) pins the document
CreationDate / ModDate and derives the PDF `/ID` from the document metadata instead
diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml
index 6e5eb31a..5c8d04c5 100644
--- a/benchmarks/pom.xml
+++ b/benchmarks/pom.xml
@@ -7,7 +7,7 @@
io.github.demchaav
graph-compose-build
- 2.0.1-SNAPSHOT
+ 2.1.0-SNAPSHOT
../pom.xml
diff --git a/bundle/pom.xml b/bundle/pom.xml
index 5ad64177..40b97ed7 100644
--- a/bundle/pom.xml
+++ b/bundle/pom.xml
@@ -32,7 +32,7 @@
graph-compose and graph-compose-templates dependencies below use
${project.version}, so they follow automatically.
-->
- 2.0.1-SNAPSHOT
+ 2.1.0-SNAPSHOT
jar
GraphCompose Bundle
diff --git a/core/pom.xml b/core/pom.xml
index 1cd3357d..5935d780 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -6,7 +6,7 @@
io.github.demchaav
graph-compose-core
- 2.0.1-SNAPSHOT
+ 2.1.0-SNAPSHOT
GraphCompose Core
A declarative layout engine for programmatic document generation, implemented primarily in Java. This is the lean engine coordinate; depend on the `graph-compose` artifact for the drop-in, PDF-capable install.
diff --git a/core/src/main/java/com/demcha/compose/document/dsl/LineBuilder.java b/core/src/main/java/com/demcha/compose/document/dsl/LineBuilder.java
index 6b5fe1d4..3f7ebc8a 100644
--- a/core/src/main/java/com/demcha/compose/document/dsl/LineBuilder.java
+++ b/core/src/main/java/com/demcha/compose/document/dsl/LineBuilder.java
@@ -289,7 +289,7 @@ public LineBuilder fill() {
* {@link SectionBuilder#keepWithNext()}.
*
* @return this builder
- * @since 2.0.0
+ * @since 2.1.0
*/
public LineBuilder keepWithNext() {
this.keepWithNext = true;
@@ -301,7 +301,7 @@ public LineBuilder keepWithNext() {
*
* @param value true to keep the line with the next block
* @return this builder
- * @since 2.0.0
+ * @since 2.1.0
*/
public LineBuilder keepWithNext(boolean value) {
this.keepWithNext = value;
diff --git a/core/src/main/java/com/demcha/compose/document/dsl/SectionBuilder.java b/core/src/main/java/com/demcha/compose/document/dsl/SectionBuilder.java
index 78972e64..131bbef9 100644
--- a/core/src/main/java/com/demcha/compose/document/dsl/SectionBuilder.java
+++ b/core/src/main/java/com/demcha/compose/document/dsl/SectionBuilder.java
@@ -60,7 +60,7 @@ public SectionBuilder keepTogether(boolean value) {
* nothing follows the section on the page.
*
* @return this builder
- * @since 2.0.0
+ * @since 2.1.0
*/
public SectionBuilder keepWithNext() {
this.keepWithNext = true;
@@ -72,7 +72,7 @@ public SectionBuilder keepWithNext() {
*
* @param value true to keep the section with the first line of the next block
* @return this builder
- * @since 2.0.0
+ * @since 2.1.0
*/
public SectionBuilder keepWithNext(boolean value) {
this.keepWithNext = value;
diff --git a/core/src/main/java/com/demcha/compose/document/layout/NodeDefinition.java b/core/src/main/java/com/demcha/compose/document/layout/NodeDefinition.java
index fdad87c1..49f4411d 100644
--- a/core/src/main/java/com/demcha/compose/document/layout/NodeDefinition.java
+++ b/core/src/main/java/com/demcha/compose/document/layout/NodeDefinition.java
@@ -132,7 +132,7 @@ default List emitOverlayFragments(PreparedNode prepared,
*
* @param prepared prepared node whose first slice is being measured
* @return the first slice's height in points
- * @since 2.0.0
+ * @since 2.1.0
*/
default double firstSliceHeight(PreparedNode prepared) {
return prepared.measureResult().height();
diff --git a/core/src/main/java/com/demcha/compose/document/node/DocumentNode.java b/core/src/main/java/com/demcha/compose/document/node/DocumentNode.java
index 8c8943d4..bc03759d 100644
--- a/core/src/main/java/com/demcha/compose/document/node/DocumentNode.java
+++ b/core/src/main/java/com/demcha/compose/document/node/DocumentNode.java
@@ -99,7 +99,7 @@ default boolean keepTogether() {
* siblings relocate together, with the break hoisted before the run.
*
* @return true to keep this node with the first line of the following block
- * @since 2.0.0
+ * @since 2.1.0
*/
default boolean keepWithNext() {
return false;
diff --git a/coverage/pom.xml b/coverage/pom.xml
index e2aa25e2..0a509384 100644
--- a/coverage/pom.xml
+++ b/coverage/pom.xml
@@ -29,7 +29,7 @@
io.github.demchaav
graph-compose-build
- 2.0.1-SNAPSHOT
+ 2.1.0-SNAPSHOT
../pom.xml
diff --git a/examples/pom.xml b/examples/pom.xml
index 26c76243..1c04bab0 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -7,7 +7,7 @@
io.github.demchaav
graph-compose-build
- 2.0.1-SNAPSHOT
+ 2.1.0-SNAPSHOT
../pom.xml
diff --git a/pom.xml b/pom.xml
index 9b29c825..64e9f661 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
io.github.demchaav
graph-compose-build
- 2.0.1-SNAPSHOT
+ 2.1.0-SNAPSHOT
pom
GraphCompose Build Aggregator
diff --git a/qa/pom.xml b/qa/pom.xml
index 88100b32..0866454e 100644
--- a/qa/pom.xml
+++ b/qa/pom.xml
@@ -22,7 +22,7 @@
io.github.demchaav
graph-compose-build
- 2.0.1-SNAPSHOT
+ 2.1.0-SNAPSHOT
../pom.xml
diff --git a/render-docx/pom.xml b/render-docx/pom.xml
index 5f01e93e..b705b658 100644
--- a/render-docx/pom.xml
+++ b/render-docx/pom.xml
@@ -19,7 +19,7 @@
-->
io.github.demchaav
graph-compose-render-docx
- 2.0.1-SNAPSHOT
+ 2.1.0-SNAPSHOT
GraphCompose Render — DOCX
Semantic DOCX export backend for GraphCompose, backed by Apache POI.
diff --git a/render-pdf/pom.xml b/render-pdf/pom.xml
index 4947d6b7..445eb319 100644
--- a/render-pdf/pom.xml
+++ b/render-pdf/pom.xml
@@ -25,7 +25,7 @@
-->
io.github.demchaav
graph-compose-render-pdf
- 2.0.1-SNAPSHOT
+ 2.1.0-SNAPSHOT
GraphCompose Render — PDF
The PDFBox-backed PDF render backend for GraphCompose.
diff --git a/render-pdf/src/main/java/com/demcha/compose/document/backend/fixed/pdf/PdfRenderEnvironment.java b/render-pdf/src/main/java/com/demcha/compose/document/backend/fixed/pdf/PdfRenderEnvironment.java
index 42471903..ec8a07b4 100644
--- a/render-pdf/src/main/java/com/demcha/compose/document/backend/fixed/pdf/PdfRenderEnvironment.java
+++ b/render-pdf/src/main/java/com/demcha/compose/document/backend/fixed/pdf/PdfRenderEnvironment.java
@@ -123,6 +123,7 @@ public org.apache.pdfbox.pdmodel.PDPage documentPage(int localPageIndex) {
*
* @param alpha non-stroking alpha constant in {@code [0, 1]}
* @return shared graphics state owned by the current render pass
+ * @since 2.1.0
*/
public PDExtendedGraphicsState fillAlphaState(float alpha) {
return fillAlphaStates.computeIfAbsent(alpha, value -> {
@@ -139,6 +140,7 @@ public PDExtendedGraphicsState fillAlphaState(float alpha) {
*
* @param alpha stroking alpha constant in {@code [0, 1]}
* @return shared graphics state owned by the current render pass
+ * @since 2.1.0
*/
public PDExtendedGraphicsState strokeAlphaState(float alpha) {
return strokeAlphaStates.computeIfAbsent(alpha, value -> {
diff --git a/render-pptx/pom.xml b/render-pptx/pom.xml
index 26a8911d..9bbf437a 100644
--- a/render-pptx/pom.xml
+++ b/render-pptx/pom.xml
@@ -19,7 +19,7 @@
-->
io.github.demchaav
graph-compose-render-pptx
- 2.0.1-SNAPSHOT
+ 2.1.0-SNAPSHOT
GraphCompose Render — PPTX
PPTX render backends for GraphCompose: the coordinate-exact fixed-layout backend (POI XSLF) and the slide-safe semantic export skeleton.
diff --git a/templates/pom.xml b/templates/pom.xml
index 34028cd3..1a05d320 100644
--- a/templates/pom.xml
+++ b/templates/pom.xml
@@ -17,7 +17,7 @@
-->
io.github.demchaav
graph-compose-templates
- 2.0.1-SNAPSHOT
+ 2.1.0-SNAPSHOT
GraphCompose Templates
Built-in CV, cover-letter, invoice, and proposal document templates for GraphCompose.
diff --git a/templates/src/main/java/com/demcha/compose/document/templates/core/text/MarkdownInline.java b/templates/src/main/java/com/demcha/compose/document/templates/core/text/MarkdownInline.java
index 32da854d..cddfebf3 100644
--- a/templates/src/main/java/com/demcha/compose/document/templates/core/text/MarkdownInline.java
+++ b/templates/src/main/java/com/demcha/compose/document/templates/core/text/MarkdownInline.java
@@ -108,6 +108,7 @@ public static void appendTrimmed(RichText rich, String text,
* @param text source string; null treated as empty
* @param baseStyle style applied to the transformed runs
* @param displayTransform transform applied to visible text (never the url)
+ * @since 2.1.0
*/
public static void appendTransformed(RichText rich, String text,
DocumentTextStyle baseStyle,
@@ -133,6 +134,7 @@ public static void appendTransformed(RichText rich, String text,
* @param rich target rich-text builder
* @param text source string; null treated as empty
* @param baseStyle style applied to the upper-cased runs
+ * @since 2.1.0
*/
public static void appendUpperCased(RichText rich, String text,
DocumentTextStyle baseStyle) {
@@ -180,6 +182,7 @@ public static void appendPlainIfPresent(RichText rich, String prefix,
* @param prefix separator prepended before the value (never a link)
* @param value source string; null treated as empty
* @param style style applied to the prefix and the value's plain runs
+ * @since 2.1.0
*/
public static void appendIfPresent(RichText rich, String prefix,
String value,
diff --git a/testing/pom.xml b/testing/pom.xml
index 3fa18625..784ca16e 100644
--- a/testing/pom.xml
+++ b/testing/pom.xml
@@ -19,7 +19,7 @@
-->
io.github.demchaav
graph-compose-testing
- 2.0.1-SNAPSHOT
+ 2.1.0-SNAPSHOT
GraphCompose Testing
Consumer testing support for GraphCompose: layout-snapshot assertions and PDF visual regression.
diff --git a/wrapper/pom.xml b/wrapper/pom.xml
index 2e9978d9..869b9efb 100644
--- a/wrapper/pom.xml
+++ b/wrapper/pom.xml
@@ -22,7 +22,7 @@
-->
io.github.demchaav
graph-compose
- 2.0.1-SNAPSHOT
+ 2.1.0-SNAPSHOT
GraphCompose
The graph-compose coordinate: a drop-in aggregator over graph-compose-core for a PDF-capable install.