This release includes breaking changes for platform teams planning a safe upgrade.
✓ No known CVEs patched in this version
Topics
+12 more
Summary
AI summaryBroad release touches Architecture, Tests, Migration from v1.4.x, and docs/migration-v1-4-to-v1-5.md.
Full changelog
Headline — "intuitive"
v1.5 keeps every v1.4 cinematic primitive and turns the canonical
authoring surface into a polished, theme-driven experience. Three new
visual feature pillars — shape-as-container with clip path,
transforms (rotate / scale) + per-layer z-index, and advanced
tables — combine with two new cinematic templates
(InvoiceTemplateV2, ProposalTemplateV2), a CvTheme ↔
BusinessTheme bridge (ADR 0002), six modernised CV templates,
and a documentation pass that covers every new primitive with a recipe
and a runnable example. Test count grew from 525 (v1.4.1) to 675 — an
extra +150 tests across the cinematic, transform, table, theme-bridge,
streaming, snapshot, CV-render, and Transformable-leaf-builder surfaces.
v1.5 is fully source-compatible with v1.4. Every public record
that grew a new field ships back-compat constructors that default the
new value, so v1.4 callers compile and behave unchanged. See
docs/migration-v1-4-to-v1-5.md.
Public API — visual primitives
- Shape-as-container. New
addCircle(diameter, fill, inside),
addEllipse(w, h, fill, inside), andaddContainer(...)shortcuts
onAbstractFlowBuilderbuild aShapeContainerNodewhose bounding
box is dictated by aShapeOutline(Rectangle,
RoundedRectangle,Ellipse, plus acircle(diameter)factory).
Children are clipped via the newClipPolicyenum
(CLIP_PATH— default — /CLIP_BOUNDS/OVERFLOW_VISIBLE). The
PDF backend honours every clip policy via graphics-state
saveGraphicsState() + clip(path)markers; the DOCX backend renders
layers inline without the outline frame and logs a one-time
docx.export.shape-container-fallbackcapability warning.
ShapeContainerBuilderexposes the same nine-point alignment
vocabulary asLayerStackBuilderplusposition(node, dx, dy, anchor)for screen-space nudges. - Transforms (rotate / scale). New
com.demcha.compose.document.style.DocumentTransformvalue type
withrotate(deg),scale(uniform),scale(sx, sy)factories
pluswithRotation(...)/withScale(...)axis-preserving copies
and anisIdentity()helper. New
com.demcha.compose.document.dsl.Transformable<T>mixin exposes
transform(...),rotate(...),scale(...)as default methods.
Every shape-shaped builder opts in:ShapeContainerBuilder,
ShapeBuilder,LineBuilder,EllipseBuilder,ImageBuilder,
BarcodeBuilder.rotate(...).scale(...)chain naturally and pivot
around the placement centre. The PDF backend issues
saveGraphicsState() + cm(matrix)around each transformed leaf
(rotation is negated on the way out so the engine's clockwise
convention matches PDF native counter-clockwise). Identity
transforms short-circuit and emit no markers, so layout snapshots
for default-configured nodes are byte-identical to v1.4. - Per-layer z-index.
LayerStackNode.Layerand shape-container
layers gainint zIndex(default0).
LayerStackBuilder.layer(node, align, zIndex)/
position(node, dx, dy, align, zIndex)and the matching
ShapeContainerBuilderoverloads let a layer declared earlier draw
on top of layers declared later. The layout compiler stable-sorts
layers before render; equalzIndexkeeps source order.
Public API — advanced tables
DocumentTableCell.rowSpan(int)mirrors the existing
colSpan(int). Cells compose freely:
DocumentTableCell.text("Tall").colSpan(2).rowSpan(3). The layout
layer skips occupied grid positions when interpreting subsequent
source rows; misalignments (missing cell, extra source cell,
overlapping span, span exceeding remaining rows) raise precise
diagnostics.TableBuilder.zebra(odd, even)paints alternating row fills.
Available as(DocumentTableStyle, DocumentTableStyle)and as a
(DocumentColor, DocumentColor)overload. Either argument may be
nullto skip painting that parity. Existing entries in the
rowStylesmap (headerStyle(...),rowStyle(idx, ...),
totalRow(...)) always win over zebra alternation.TableBuilder.totalRow(values)adds a totals row with a default
bold-on-grey-blue style;totalRow(style, values)is the
customisable form.TableBuilder.repeatHeader()/repeatHeader(rowCount)re-emits
the configured leading rows at the top of every continuation page
when a table paginates. Default is0so existing tables paginate
exactly as before.TableBuilder.headerRow(values)is a naming alias for
header(...)so authors writing
headerRow(...).row(...).totalRow(...)keep a parallel vocabulary.
Public API — templates and themes
InvoiceTemplateV2is the cinematic invoice counterpart to
InvoiceTemplateV1. Two constructors: the no-arg form picks
BusinessTheme.modern(), the one-arg
InvoiceTemplateV2(BusinessTheme)accepts any theme. Hero
softPanelcarrying invoice number / dates / inline rich-text
status, a two-column row withFrom/Bill toparties, themed
line-items table withheaderStyle/ zebra / totals /
repeatHeader(), and a footer row withaccentLeftstrips on the
notes / payment-terms columns.ProposalTemplateV2is the proposal counterpart, sharing the
sameBusinessTheme-driven composition: hero panel rounded only on
the right (via the newDocumentCornerRadius.right(...)form),
themed executive-summary panel, sender / recipient parties row,
sections rendered throughtheme.text().h2()headings, a timeline
table (Phase / Duration / Details), and a pricing table (Item /
Description / Amount) withrepeatHeader(), zebra rows, and a
total-pricing row anchored at the bottom viatotalRow(...).CvTheme.fromBusinessTheme(BusinessTheme)static factory
derives a CV theme from a business theme (ADR 0002). The bridge
maps palette / text-scale slots intoprimaryColor/
secondaryColor/bodyColor/accentColor/headerFont/
bodyFont/ font sizes; CV-specific layout tokens (spacing,
moduleMargin,spacingModuleName) keep the existing CV defaults.
The ten existing CV templates andCvTemplateV1continue to work
unchanged.- Six CV templates modernised to v1.5 idioms:
BlueBannerCvTemplate,BoxedSectionsCvTemplate,
CenteredHeadlineCvTemplate,MonogramSidebarCvTemplate,
SidebarPortraitCvTemplate,TimelineMinimalCvTemplate. Each
gains a(CvTheme)constructor and keeps a no-arg one whose
default theme matches the legacy palette/font choices, so default-
constructed instances render identical-page-count PDFs to v1.4.
accentTop/accentBottomreplace the old
addLine(horizontal=innerWidth)separators around section banners,
andsoftPanel(...)collapses the
padding(asymmetric) + fillColor(...)cascade. InvoiceTemplateV1andProposalTemplateV1continue to ship
side-by-side. Authors who want the cinematic look opt in by
switching the type.
Public API — DSL ergonomics (Phase A)
LayerStackBuilderexposes nine alignment shortcuts (topLeft,
topCenter,topRight,centerLeft,center,centerRight,
bottomLeft,bottomCenter,bottomRight) on top ofback/
centerso authors do not need to remember the fullLayerAlign
enum.LayerStackBuilder.position(node, offsetX, offsetY, anchor)nudges
a layer from its anchor by an on-screen offset (positiveoffsetX
= right, positiveoffsetY= down).AbstractFlowBuildergains five convenience overloads on top of
the v1.4 surface:addShape(w, h, fill),
addEllipse(diameter, fill),addEllipse(w, h, fill),
addCircle(diameter, fill),addImage(data, w, h).RowBuilder.spacing(double)is the canonical name for horizontal
child spacing;RowBuilder.gap(double)becomes a deprecated alias
(@Deprecated(since = "1.5.0")) that delegates tospacing(...).RowBuilder.add(node)validates the child type eagerly and
raisesIllegalArgumentExceptionfrom the offending call site
instead of deferring tobuild()and raising
IllegalStateExceptionlater.DocumentDsl.richText(Consumer<RichText>)is a new callback entry
point that builds aRichTextrun sequence in one fluent call.
Architecture
- New
NodeDefinition.emitOverlayFragments(...)hook complements the
existingemitFragments(...). It exists for paired begin/end
marker pairs (clip-begin/end, transform-begin/end) so the layout
compiler can emit a single flat fragment sequence
[transform-begin → outline → clip-begin → … layers … → clip-end → transform-end]in one pass. Most node types inherit the empty
default and need no changes. - New marker payloads on
BuiltInNodeDefinitions:
ShapeClipBeginPayload/ShapeClipEndPayload(carry outline +
policy + owner path),TransformBeginPayload/
TransformEndPayload. PDF render handlers ship alongside:
PdfShapeClipBeginRenderHandler,PdfShapeClipEndRenderHandler,
PdfTransformBeginRenderHandler,PdfTransformEndRenderHandler,
registered inPdfFixedLayoutBackend.defaultHandlers(). - New
PaginationPolicy.SHAPE_ATOMICdistinguishes shape-clipped
atomicity from bbox-onlyATOMICfor snapshots and render
handlers. Oversized containers raise the existing
AtomicNodeTooLargeExceptionwith the offending semantic name. TableLayoutSupportreplaces the per-rowcolSpan-sum check with
a unified cell-grid pre-pass driven by an occupancy mask. The new
buildLogicalRows(node, columnCount)walks columns left-to-right,
skipping positions covered by a prior row's spanning cell.
LogicalCellcarries the cell's full
(startRow, startColumn, colSpan, rowSpan, content)extent.
Row-height resolution is two-pass: single-row first, then spanning
cells distribute deficit equally across covered rows.TableResolvedCellgainsdouble yOffset(eighth field). Spanning
cells use a NEGATIVE offset equal to the cumulative height of the
rows below the starting row, so the cell's rectangle extends
downward through the rows it merges instead of upward beyond the
starting row. Both PDF row-render handlers honour the offset.TableNodegains a 12th fieldint repeatedHeaderRowCount
(default0).TableDefinition.splithonours the field: the tail
slice is built withprependHeaderRowCount = headerCountso each
continuation carries the header at the top.LayoutCompiler.compileStackedLayerand the STACK branch of
compileNodeInFixedSlotcompute a stableiterationOrder
permutation viastableZIndexOrder(...)before iterating the
layer list. Stable on ties → equalzIndexkeeps source order.BuiltInNodeDefinitions.PreparedStackLayoutgains a fourth list
zIndices: List<Integer>populated by both
ShapeContainerDefinitionandLayerStackDefinition.- New ADR
docs/adr/0001-shape-as-container.mdrecords the
"separate semantic type" decision (rejected: a clip flag on the
existingLayerStackNoderecord). - New ADR
docs/adr/0002-theme-unification.mdrecords the phased
approach toCvTheme↔BusinessTheme(rejected: a common
Themeinterface that loses CV-specific vocabulary).
Examples
The runnable examples/ module gains six new showcases hooked into
GenerateAllExamples:
ShapeContainerExample— circles, ellipses, rounded cards with
clipped layers (ClipPolicy.CLIP_PATH).TransformsExample— three-circle rotate row (15° / -15° / no
tilt), three-card scale row (scale(0.7),scale(1.1, 0.85),
identity), and a z-swap stage where a RED square declared first
withzIndex = 10draws on top of a TEAL square declared second.TableAdvancedExample— hero callout, a 3-row spanning side note,
and a 36-row invoice with bold-on-teal repeating header, zebra
body rows, and a gold totals row.CustomBusinessThemeExample— a hand-built "Studio Emerald"
BusinessThemeconstructed from rawDocumentPalette/
SpacingScale/TextScale/TablePresetrecords (no factory
shortcut), feedingInvoiceTemplateV2.HttpStreamingExample—writePdf(OutputStream)for Servlet /
S3 / GCS adopters. Includes a Spring Boot@RestController
snippet in the Javadoc and aTrackingOutputStreamtest that
proves the caller's stream is not closed.LayoutSnapshotRegressionExample— full
compose →layoutSnapshot()→LayoutSnapshotJson.toJson(...)
workflow with a copy-and-paste baseline / drift-report pattern,
plus a pointer to the production
LayoutSnapshotAssertions.assertMatches(document, "...")helper
for in-test usage.WeeklyScheduleFileExamplerewritten to delegate to a new reusable
examples/support/WeeklyScheduleRenderer. The renderer's typed
surface —JobTitleenum,StaffMember/DayPlan/Shift
records, sealed-interfaceHalfandDayShifttypes with factory
methods (DayShift.OFF,.acrossDay(start, end, ShiftType.STOCK),
.shifts(lunchStart, lunchEnd, dinnerStart, dinnerEnd),
.lunchOnly(...),.dinnerOnly(...),
.halves(Half.shift(...), Half.STANDBY)) — replaces the cryptic
string tokens used previously.Theme(withaurora()default and
a per-ShiftStatuscolour map) andLayout(page size + margin +
column widths) records keep every colour and dimension out of the
renderer's static state, so re-skinning the schedule is a
swap-one-record call. Auto-fills the seven day labels from a
LocalDate weekStart, sorts staff byJobTitle.ordinal(), and
emits a separator row at every job-title boundary so adding or
removing aStaffMembernever requires updating positional indices.
The example file shrinks from ~700 lines of literal data to ~180
lines of typed declarations.
Documentation
- README quick-start refreshed to open with a
BusinessTheme.modern()-driven hero (softPanel+accentLeft+
theme.text().h1()); the plain-text DSL stays underneath for
callers who do not want a theme. - New "v1.5 sample renders (PDF)" section links six committed PDFs
underassets/readme/v1.5/so the README works without running
anything. - New
examples/README.mdexamples gallery —
every example listed with description, key code snippet, committed
PDF preview, and source link, grouped by category (built-in
templates / cinematic templates / v1.5 feature showcases / public-
API surface / production patterns / operational documents).
Committed PDF previews of all 22 examples live under
assets/readme/examples/(whitelisted in
.gitignore) so users can browse renders straight from GitHub
without running anything. - New
docs/template-authoring.md(~620
lines) — the canonical cheatsheet covering builder hierarchy, a
per-builder one-liner cheatsheet, a style-types reference, the
theme system in 60 seconds, six golden patterns, ten anti-patterns,
a 40-lineStatusReportTemplateV1skeleton, and a "where to look
next" map. - New recipes:
docs/recipes/shape-as-container.mddocs/recipes/transforms.mddocs/recipes/tables.md(row span /
zebra / totals / repeating header)docs/recipes/shapes.md(filled cards,
dividers, spacers, lines, ellipses, image fit, soft panels)docs/recipes/extending.md
docs/recipes.mdis now a pure index linking
every topic-focused recipe page plus four 5-line "common DSL
primitives" starter snippets.docs/canonical-legacy-parity.md
gains a "Shape-as-container (clipped)" row recording the DOCX
fallback rule.- New
docs/migration-v1-4-to-v1-5.md
— fresh migration guide for v1.4 consumers.
Performance — v1.5 baseline
CurrentSpeedBenchmark smoke profile (single-thread, 30 warmup +
100 measurement iterations per scenario) recorded on Java 21,
Windows 11. All five scenarios are well within healthy production
ranges.
| Scenario | Avg ms | p50 ms | p95 ms | Docs/sec | Peak MB |
|---|---:|---:|---:|---:|---:|
| engine-simple | 2.25 | 1.96 | 4.20 | 444.60 | 22 |
| invoice-template (V1) | 13.39 | 13.12 | 17.55 | 74.67 | 182 |
| cv-template (V1) | 6.94 | 6.58 | 10.18 | 144.02 | 78 |
| proposal-template (V1) | 15.77 | 15.50 | 18.31 | 63.43 | 182 |
| feature-rich | 36.80 | 32.06 | 35.51 | 27.18 | 94 |
Stage breakdown (median ms per stage):
| Scenario | Compose | Layout | Render | Total |
|---|---:|---:|---:|---:|
| invoice-template | 0.249 | 2.774 | 6.042 | 9.312 |
| cv-template | 0.173 | 2.343 | 1.544 | 4.087 |
| proposal-template | 0.256 | 8.715 | 5.345 | 14.563 |
The smoke profile is single-thread by design; throughput numbers
reflect "one document at a time" latency, not concurrent throughput.
The formal "no >5% regression" gate first activates between this
baseline and the next snapshot.
Tests
- 675/675 green (was 525 on v1.4.1) — +150 new tests across:
- shape-clip-path fragment ordering and pagination invariants
(ShapeContainerBuilderTest,ShapeContainerInvariantsTest) - transform mixin contract and CTM checks
(DocumentTransformTest, the
everyTransformBeginInArbitraryDocumentHasMatchingEndOnSamePage
architecture-guard test) - per-layer z-index ordering and stable-tie behaviour
(ShapeContainerZIndexDemoTestplus the two zIndex cases on
ShapeContainerBuilderTest) - table row-span / zebra / totals / repeating-header invariants
(TableBuilderRowSpanTest,TableBuilderZebraAndTotalsTest,
TableBuilderRepeatHeaderTest) InvoiceTemplateV2/ProposalTemplateV2invariants and three-
theme demo renders
(InvoiceTemplateV2Test,InvoiceTemplateV2DemoTest,
ProposalTemplateV2Test,ProposalTemplateV2DemoTest)- custom
BusinessThemeend-to-end
(CustomBusinessThemeDemoTest) - HTTP streaming contract (
HttpStreamingDemoTest—
no-close-on-caller invariant) - layout-snapshot determinism
(LayoutSnapshotRegressionDemoTest) CvTheme.fromBusinessThememapping
(CvThemeBusinessThemeAdapterTest)- six modernised CV templates rendered to file at expected page
counts (CvTemplateRenderTest) Transformable<T>contract pinned for every leaf builder that
opted in (TransformableLeafBuildersTest): default identity
transform,rotate(...)/scale(...)propagation, identity
short-circuit emits no markers, non-identity wraps the leaf
payload with matching transform-begin / transform-end carrying the
same owner path
- shape-clip-path fragment ordering and pagination invariants
Migration from v1.4.x
RowBuilder.gap(double)is deprecated in favour of
spacing(double). The deprecated alias still compiles; CV
templates and runnable examples were migrated.RowBuilder.add(node)now throwsIllegalArgumentException
eagerly. Tests that asserted the deferredIllegalStateException
inbuild()must switch their expectation.- All other v1.4 record signatures stay backward-compatible:
LayerStackNode.Layer,ShapeContainerNode,TableNode,
DocumentTableCell,TableResolvedCell, and
BuiltInNodeDefinitions.PreparedStackLayoutship new canonical
constructors and preserve every existing constructor as a back-
compat shim that defaults the new fields.InvoiceTemplateV1and
ProposalTemplateV1ship side-by-side with the V2 templates;
callers who want the cinematic look opt in by switching the type.
See docs/migration-v1-4-to-v1-5.md
for the full guide.
Weekly OSS security release digest.
The CVE patches and breaking changes that affected production tools this week. One email, every Sunday.
No spam, unsubscribe anytime.
Share this release
About GraphCompose 2.0
All releases →Related context
Related tools
Earlier breaking changes
- v2.0.0 Package rename: layered template packages dropped `.v2` suffix; update imports.
- v2.0.0 Removed dormant ECS engine internals (EntityManager, SystemECS, Entity components, render pipeline).
- v2.0.0 Removed linkOptions() accessor; use linkTarget() and ExternalLinkTarget.options().
- v2.0.0 Removed PDF‑typed document‑chrome overloads on DocumentSession; use backend‑neutral metadata/watermark/protect/header/footer.
- v2.0.0 Removed DSL name-aliases DocumentSession.builder() and DocumentDsl.text(); use builder() and paragraph().
Beta — feedback welcome: [email protected]