All notable changes to GraphCompose are documented here. Versions follow semantic versioning; release dates are ISO 8601.
Maintenance + compatibility patch. Drops the Java 21 source/target baseline to Java 17+ so the library can ship into older enterprise stacks without a fork, and refreshes test/build dependencies. No public API change — engine, DSL, themes, templates, and backend records all stay source-compatible with v1.6.0; existing v1.6.0 callers compile and behave unchanged.
Co-developed with external contributor @jottinger (#8, #10).
- Java 17 baseline.
<maven.compiler.release>flips from21to17acrosspom.xml,examples/pom.xml, andbenchmarks/pom.xml. Engine source loses the Java 21–only constructs (switch-with-type-patterns, switch-with-deconstruction,List.getFirst(),Thread.threadId()) in favour of Java 17 –compatible forms. CI runs against Temurin JDK 17. - Dependency refresh + CVE pass. Bumps Jackson
2.20.1 → 2.21.3, Logback1.5.18 → 1.5.32, Lombok1.18.38 → 1.18.46, POI5.4.0 → 5.5.1, SnakeYAML2.4 → 2.6, AssertJ3.27.3 → 3.27.6, JUnit5.12.2 → 5.14.4, Mockito5.20.0 → 5.23.0. Adds explicit ByteBuddy1.18.7so Mockito works on the Java 25+ access rules. Maven plugin bumps:maven-compiler-plugin 3.13 → 3.15,maven-surefire-plugin 3.2.5 → 3.5.5,exec-maven-plugin 3.5 → 3.6.2.
Maven Central distribution (#7) remains on the v1.7.0 roadmap alongside the JMH benchmark migration; v1.6.1 stays on JitPack as a maintenance release.
The "expressive" release. Closes the remaining canonical-vs-legacy
parity gaps for advanced authoring without architectural rollback.
Every new primitive ships through DocumentNode + NodeDefinition + render handler. See docs/v1.6-roadmap.md
for the phased plan, verification gates, and ADRs.
- Nested list ergonomics (Phase A — landed).
ListBuilder.addItem(label, Consumer)for builder-callback child scopes; per-depth marker cascade; mixed flat / nested authoring preserves source order. ADR 0012. - Composed table cell content (Phase B — landed).
DocumentTableCell.node(DocumentNode)accepts any composable canonical node as cell content (paragraphs, lists, layer-stacks, sub-tables) with two-pass measurement. ADR 0013. - Controlled free-canvas placement (Phase C — landed).
CanvasLayerNode— pixel-precise(x, y)placement of children inside a fixed-size bounding box, withClipPolicyclipping and atomic pagination. ADR 0014. - Templates v2 preset library (committed). Canonical CV /
cover-letter / invoice / proposal surface rebuilt around four
layers (theme tokens → layout slots → components + blocks →
spec data); 14 CV presets and 14 paired cover-letter presets
with one-liner
create(BusinessTheme)factories, inline markdown, hyperlinks, and slot-based multi-column layouts. ADR 0011. - Architecture hardening (committed).
@InternalAPI stability marker, publicPdfFragmentRenderHandlerSPI,DocumentRenderingExceptionon the convenience render path, thread-safety contract documented. ADRs 0003 + 0004. - Verify gate: 819 / 0 / 0 / 0 (
mvnw verify). 26 runnable examples regenerate cleanly throughGenerateAllExamples.
The architecture lane closes the highest-severity findings from the
post-1.5 audit. None of these change author-facing behaviour for
unmodified v1.5 code; they sharpen the public-vs-internal boundary,
open extension points, and split the load-bearing files. See
docs/migration-v1-5-to-v1-6.md
for the user-facing summary.
@InternalAPI stability marker. Newcom.demcha.compose.document.api.Internalannotation (runtime-retained) marksdocument.layout.*and theBuiltInNodeDefinitionspayload records as implementation detail.InternalAnnotationCoverageTestenforces propagation. ADR 0003 records the boundary decision.DocumentRenderingExceptionwraps the convenience render path:buildPdf,writePdf,toPdfBytes, and the AutoCloseableclose()override no longer declarethrows Exception. Lower-level backend SPIs continue to declarethrows Exceptionon purpose.- Public PDF render handler SPI. The
PdfFragmentRenderHandlerJavadoc is rewritten as an extension point andPdfFixedLayoutBackend.Builder.addHandler(...)is the new registration path. Custom handlers replace built-in defaults bypayloadType(). ADR 0004 records the SPI shape. - Thread-safety contract documented on
document.api/package-info.javaanddocument.backend.fixed.pdf/package-info.java. - DSL polish.
DocumentDsl.text()andDocumentSession.builder()aliases are@Deprecated(forRemoval=true, since="1.6.0"); preferparagraph()anddsl()respectively. - PDF-typed chrome overloads on
DocumentSession—metadata,watermark,protect,header,footeracceptingPdf*Options— are@Deprecated(forRemoval=true, since="1.6.0"); the canonical backend-neutral overloads are unchanged. DocumentPalette.builder()replaces the positionalDocumentPalette.of(Color × 7)factory; the old factory is@Deprecated(forRemoval=true).BusinessTheme.classic()/modern()/executive()now use the builder.IllegalStateExceptionfrombuild()names every missing token in one message.- Targeted layout perf wins (none alter output bytes):
LayoutCompiler.compositeDecorationFragments/compositeOverlayFragmentsno longer wrap withList.copyOf,stableZIndexOrdershort-circuits when every layer reports the samezIndex,PdfRenderSessionkeeps page surfaces in aPDPageContentStream[](noInteger.valueOfautoboxing),PdfFontLoader.THREAD_LOCAL_TTF_CACHEis a bounded LRU (max 32 entries per thread). Duplicatecom.demcha.compose.font.Pdf_FontLoaderdeleted. - Layout invariant tests.
LayoutCompilerInvariantsTestpins four scenarios that previously had only transitive snapshot coverage: page-advance on overflow, layer source-order under uniformzIndex, explicitzIndexordering, equal-weight row slot distribution. BuiltInNodeDefinitionssplit (Phase E.1). All 15 built-inNodeDefinitionimplementations now live indocument.layout.definitions.*(one file per node type): PageBreak, Spacer, Shape, Line, Ellipse, Image, Barcode, Container, Section, Row, LayerStack, ShapeContainer, Table, Paragraph, List. Shared inline helpers (EPS, transform wrapping, decoration / table / measurement adapters) live inNodeDefinitionSupport; the paragraph / list text-flow cluster (wrapping, markdown tokenisation, inline-run layout, split slicing) lives in the newTextFlowSupporthelper.BuiltInNodeDefinitionsdrops from 3,037 to ~60 lines and now only exposesregisterDefaults(NodeRegistry)as the single registration entry point.PlacementContextstrategy interface (Phase E.4). A newPlacementContextsealed interface unifies the placement bookkeeping thatLayoutCompilerhelpers need (current page index, canvas, prepare/fragment contexts, target lists for placed nodes/fragments, and acanAdvancePage()/advancePage()/touchPage()strategy).FixedSlotPlacementContextpins the page for row slots, stacked layers, and atomic leaf placement;MutatingPlacementContextwraps the liveCompilerStatefor callers that drive top-down page flow. The previously private innerCompilerStateis lifted to a sibling package-private class.placeStackLayer,placeAtomicLeafFragments, andcompileNodeInFixedSlotnow takePlacementContextinstead of six explicit parameters each. Pure refactor — no public API change, no behaviour change.DocumentSessionslim (Phase E.3). NewSessionFontApifacade (session.fonts()) groupsregisterFontFamily(FontFamilyDefinition)andregisterNodeDefinition(NodeDefinition)alongside the existingchrome()andlayout()facades. Page-background composition moves from a private inner method to a newDocumentPageBackgroundsutility. The four convenience PDF methods (toPdfBytes,writePdf,buildPdf,buildPdf(Path)) share a singlewrapPdfRenderingexception-mapping helper instead of repeating the same try/catch four times. Javadoc on the deprecated PDF-typed chrome overloads is compacted to a single@deprecatedtag.DocumentSessiondrops from 1,024 to ~937 lines without changing any public method signatures.
The biggest change in v1.6 — the canonical template surface
(CV, cover letter, invoice, proposal) was rewritten from the ground
up. Old positional / cinematic-monolith composers (CvTemplateV1,
NordicCleanCvTemplate, InvoiceTemplateV2, etc.) replaced with a
four-layer architecture: **Theme tokens → Layout slots → Components
- Blocks → Spec data**, glued together by per-domain builders that preset classes wrap into one-liner factories. The result is a copy-and-tweak preset surface where adjusting one visual decision takes one method change rather than a fork of a 600-line composer.
New template package layout (replaces legacy templates/builtins,
templates/support/cv, templates/data/cv, templates/theme/CvTheme):
templates/
api/ DocumentTemplate<S>, SlotMap
themes/ Spacing, Typography (token records)
components/ Header, Module, MarkdownText
blocks/ sealed Block hierarchy:
ParagraphBlock, BulletListBlock, NumberedListBlock,
IndentedBlock, KeyValueBlock, MultiParagraphBlock
decorations/ Spacer, Divider, AccentStrip
cv/
layouts/ SingleColumn, TwoColumnSidebar, ThreeColumnMagazine
presets/ 14 flat copy-and-tweak preset classes
builder/ CvBuilder
spec/ CvSpec, CvHeader, CvModule
coverletter/
layouts/ LetterFormat
presets/ 14 paired letter presets (one per CV preset)
builder/ CoverLetterBuilder
spec/ CoverLetterSpec, CoverLetterHeader
invoice/
presets/ ModernInvoice (minimal v2 surface)
builder/ InvoiceBuilder
spec/ InvoiceSpec
proposal/
presets/ ModernProposal (minimal v2 surface)
builder/ ProposalBuilder
spec/ ProposalSpec
14 CV presets: ModernProfessional, NordicClean,
ClassicSerif, CompactMono, Executive, EngineeringResume (was
TechLeadCvTemplate), TimelineMinimal, BoxedSections,
CenteredHeadline, BlueBanner, EditorialBlue, Panel (was
ProductLeaderCvTemplate), SidebarPortrait, MonogramSidebar.
Each is one final class with one create(BusinessTheme) factory:
import com.demcha.compose.document.templates.cv.presets.ModernProfessional;
import com.demcha.compose.document.templates.cv.spec.CvSpec;
import com.demcha.compose.document.theme.BusinessTheme;
DocumentTemplate<CvSpec> template = ModernProfessional.create(BusinessTheme.modern());
template.compose(session, mySpec);Inline markdown rich text — body strings carrying
**bold** and *italic* markers render with proper
{@code DocumentTextDecoration} via the new
templates.components.MarkdownText parser. Lets an LLM emit a
resume bullet like **Java 21**, SQL, Kotlin and the preset
renders Java 21 in bold without separate inline-run construction.
Active hyperlinks — header email + LinkedIn / GitHub labels
become clickable mailto: / https: hyperlinks via
DocumentLinkOptions on per-run inline runs.
Slot-based layouts — multi-column CV presets
(Panel, SidebarPortrait, MonogramSidebar) declare named
slots (MAIN, SIDEBAR); a custom preset can rearrange which
modules go into which slot via .place(slot, "Module Name", ...).
Layout snapshot tests lock the rendered tree of every preset
(28 baselines under
src/test/resources/layout-snapshots/canonical-templates/cv-v2/
and .../coverletter-v2/).
Examples — CvTemplateGalleryFileExample renders all 14 v2
CV presets to examples/target/generated-pdfs/cv-<id>.pdf; new
CoverLetterTemplateGalleryFileExample renders all 14 paired
letter presets to cover-letter-<id>.pdf.
Migration: legacy classes have been deleted, not
deprecated. Anyone on
new CvTemplateV1() / new NordicCleanCvTemplate() / etc. must
switch to the new factory:
| Old | New |
|---|---|
new CvTemplateV1() |
ModernProfessional.create(BusinessTheme.modern()) |
new NordicCleanCvTemplate() |
NordicClean.create(BusinessTheme.modern()) |
CvTheme.defaultTheme() |
BusinessTheme.modern() + Spacing.compact() |
CvTemplate interface |
DocumentTemplate<CvSpec> |
InvoiceTemplateV2 and ProposalTemplateV2 (cinematic) remain
in templates/builtins/ as the recommended path for fully-styled
output; the new ModernInvoice / ModernProposal v2 presets
provide the canonical builder seam, with cinematic feature parity
landing in a follow-up.
The first Phase E.1 pass shipped visually-broken renders.
Every CV preset rendered as a teal-tinted single-column
ModernProfessional clone — NordicClean lost its sidebar and
soft-tinted PROFILE panel, BlueBanner lost its full-width
section banners, MonogramSidebar lost its monogram badge,
SidebarPortrait lost its portrait sidebar, ClassicSerif
lost its two-page editorial structure, and so on. The
ModernProfessionalVisualParityTest was a smoke test
(assertThat(output).exists()) and PresetLayoutSnapshotTest
recorded baselines from the new (broken) v2 renders without
comparing against V1, so the regressions sailed through CI.
Phase E.1 was reopened. All 14 CV preset renders + 14
cover-letter pair renders were rebuilt against the V1 visual
references (committed assets/readme/examples/cv-*.pdf for
the 7 presets that had a baseline; V1 source code under
docs/private/v1-reference/ — gitignored — for the rest).
The author-facing API stays stable; only the rendered output
changed.
- Adaptive sidebar fill —
SidebarPortraitandMonogramSidebarsize the trailing spacer dynamically fromcanvas().innerHeight()so the SIDEBAR_BG fill reaches the bottom of the page on A4 / Letter / smaller test fixtures without overflowing the row's page capacity. HeaderAPI gained three fluent overrides —withNameStyle(DocumentTextStyle),withContactStyle(DocumentTextStyle),withLinkStyle(DocumentTextStyle). Required for V1-parity palette (e.g. slate-blue name + royal-blue underlined links for ModernProfessional) — the unstyledHeader.rightAlignedrendered names with the activeBusinessTheme'sh1()colour instead.CvHeader.jobTitlefield added for the subtitle rendered under the name by presets that surface it (EditorialBlue, Panel, SidebarPortrait, MonogramSidebar). Falls back to a placeholder string when the spec leaves it empty.- Markdown rendering routed through
MarkdownText.parsein every CV / cover-letter preset paragraph body so spec-author bold / italic markers (**bold**/*italic*) carry through to the rendered runs — previously the paragraphs stripped markdown. - Sample data factory updated so
Education/ProjectsuseMultiParagraphBlockwith markdown bold prefixes (**MSc Computer Science** - University of Manchester | 2021) rather thanIndentedBlock's multi-line shape, andAdditional InformationcarriesKeyValueBlockentries (Languages / Work Eligibility) for bold-key + plain-value rendering. - Snapshot baselines regenerated — 28
*.jsonfiles undersrc/test/resources/layout-snapshots/canonical-templates/cv-v2/andcoverletter-v2/updated to lock the V1-parity render in place. - Pixel-diff visual parity gate landed.
PresetVisualParityTest(one for CV, one for cover letters) rasterises each preset's PDF page 0 (andclassic_serif's page 1) via PDFBoxPDFRendererand asserts per-pixel diff against a checked-in baseline PNG with budget 2500 mismatched pixels at per-channel tolerance 8 (pertemplates-restructure-plan.mdsec 6.2). 29 baselines undersrc/test/resources/visual-baselines/{cv-v2,coverletter-v2}/. Re-bless with-Dgraphcompose.visual.approve=true. ThePdfVisualRegressionharness was already built; the reopen plugged the 28 presets into it. The placeholderModernProfessionalVisualParityTestsmoke test is deleted.mvnw verify→ 792 / 0 / 0 / 0.
Tech debt (deferred to v1.7 as Phase E.4): 13 of the 14
v2 CV presets are implemented as hand-coded
DocumentTemplate subclasses driving the canonical
PageFlow DSL directly (≈ 400-700 LOC each) rather than thin
recipes through the slot-based CvBuilder. This was an
explicit trade-off during the reopen — restoring V1 visual
fidelity required components the v2 library hadn't grown
yet (Panel.softTinted, TwoColumnSidebar.tinted,
SectionStyle.uppercaseRule, WorkEntryRenderer). The
component library extension + preset refactor is tracked in
docs/private/templates-restructure-plan.md Phase E.4 and
docs/private/templates-v2-audit-remediation.md.
- Nested list ergonomics (Phase A — landed).
ListBuilder.addItem(String label, Consumer<ListBuilder> body)appends a nested item with a builder-callback child scope. NewListItemrecord carries(label, marker, children).ListNodegains anestedItemscomponent (record now has 12) and a back-compat 11-component constructor matching the v1.5 shape. Per-depth marker resolution: item-level marker wins, thenListBuilder.markerFor(int depth, ListMarker)overrides, then the built-in cascade (•→◦→▪→·). The internalusedNestedAuthoringflag preserves source order across mixed flat / nested entries — flat-only callers still get the v1.5 flatListNode. Layout flattens the tree depth-first into indent-prefixed paragraph fragments using non-breaking spaces (U+00A0) for the per-depth indent so the paragraph wrap pipeline preserves them. ADR 0012 records theListNode-extension-vs-new-NestedListNodedecision. Snapshot baseline:src/test/resources/layout-snapshots/document/nested_list_three_levels.json.mvnw verify→ 804 / 0 / 0 / 0. - Composed table cell content (Phase B — landed).
DocumentTableCell.node(DocumentNode)factory accepts any composable canonical node as cell content;DocumentTableCellgains a 5th componentDocumentNode contentwith explicit 4-arg / 3-arg / 2-arg back-compat constructors so v1.5 plain-text callers compile unchanged.TableLayoutSupportthreadsPrepareContextthroughresolveTableLayoutand prepares each composed cell's child against the cell's resolved inner width before row-height resolution; the prepared height feeds the existing two-pass row-height pass.FragmentContextgains a defaultemitChildFragments(PreparedNode, FragmentPlacement)method thatDocumentLayoutPassContextoverrides to dispatch through the registeredNodeDefinition— so any node type works inside a cell automatically (paragraph, list, layer-stack, sub-table). Pagination preserves row-by-row behaviour: a composed cell stays atomic on its row, andsliceTablePreparedNodesubsets the prepared-content map to the slice's row range while keeping repeat-header keys intact. The PDF table render handler is unchanged: it still iteratescell.lines()(empty for composed cells) and the child fragments render through their own already-registered handlers. ADR 0013 records the extend-vs-new-hierarchy and recursion-vs-special-case decisions. Snapshot baseline:src/test/resources/layout-snapshots/document/table_cell_with_paragraph.json.mvnw verify→ 810 / 0 / 0 / 0.
- Controlled free-canvas (Phase C — landed). New
CanvasLayerNodeatomic composite accepts children at explicit(x, y)pixel coordinates inside a fixed-size bounding box. Coordinates use the screen convention:(0, 0)is the canvas's top-left, positivexextends right, positiveyextends downward. NewCanvasChildrecord carries(node, x, y).CanvasLayerBuilderexposesposition(child, x, y),size(width, height),clipPolicy(...)and is plumbed throughAbstractFlowBuilder.addCanvas(width, height, Consumer).CanvasLayerDefinitionreuses the existingLayerStackNodeplacement plumbing — every child anchors atLayerAlign.TOP_LEFTand the canvas's(x, y)maps one-to-one onto the stack layout's(offsetX, offsetY). Pagination is atomic; clip policy defaults toClipPolicy.CLIP_BOUNDSand reuses theShapeContainerNodeclipping pipeline. The canvas's measured size is explicit (independent of children) so the surrounding flow reserves a deterministic rectangle. ADR 0014 records whyCanvasLayerNodeis a separate node and why absolute placement is rejected as a global policy onRowBuilder/SectionBuilder. Snapshot baseline:src/test/resources/layout-snapshots/document/canvas_layer_basic.json. Showcase:examples/.../CanvasLayerExample.java.mvnw verify→ 819 / 0 / 0 / 0.
These were on the v1.6 stretch list and did not land in time; they carry over to v1.7.
- Phase D — Real PPTX semantic export. Build out
PptxSemanticBackendfrom the existing manifest skeleton to a working POI-based exporter (paragraphs → text boxes, tables → PowerPoint tables, sections → slides). - Phase E — Maven Central distribution. Sonatype OSSRH + GPG
signing + automated deployment on tag push. Primary install
coordinates switch to
io.github.demchaav:graphcompose:1.7.0; JitPack stays documented as a fallback. - Phase F — Benchmark infrastructure modernisation. Replace the
custom warmup / measurement harness with
org.openjdk.jmhfor JIT-aware measurement, dead-code elimination protection, and proper statistical output. Move the benchmark suite (currently in test scope:CurrentSpeedBenchmark,ComparativeBenchmark,ScalabilityBenchmark,FullCvBenchmark,GraphComposeBenchmark) into a separatebenchmarks/Maven module mirroring theexamples/pattern, with a self-executing JMH jar built viamaven-shade-plugin. Add a standalonelayoutGraph()-only scenario so the README can publish a true Layout-vs-Render table backed by independently measured values rather than stage breakdown subtractions. CI Performance Smoke Check switches to the new JMH jar;scripts/run-benchmarks.ps1becomes a thin wrapper so the documented workflow keeps working.
- No revival of
GraphCompose.pdf(...)or publicEntityManager. - No nested rows or nested tables inside
RowBuilder(preserves pagination contract). - No DOCX path-clipping or transform support (Apache POI limit).
- No deprecation of v1.4 / v1.5 public records — back-compat constructors stay.
- PDFBox 3.0.7. Bumped from 3.0.5 to 3.0.7 (Apache PDFBox patch release with upstream rendering and security fixes). No public-API impact for GraphCompose consumers.
ShapeContainerVisualRegressionTesttolerates the cross-platform PDF font-rendering drift that surfaces between Windows-rendered baselines and the Linux CI runner (~1-2% pixel diff), via a calibratedmismatchedPixelBudgetinstead of bit-exact comparison.DocumentationCoverageTestno longer pins to the structural section anchors that the v1.5.0 README slim removed; the guard now scans the whole README for canonical-DSL coverage and legacy-API leakage in one whole-file pass.
This is a maintenance patch release. There are no public API changes; v1.5.0 consumers can upgrade with no code changes.
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.
- 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-statesaveGraphicsState() + clip(path)markers; the DOCX backend renders layers inline without the outline frame and logs a one-timedocx.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. Newcom.demcha.compose.document.dsl.Transformable<T>mixin exposestransform(...),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 issuessaveGraphicsState() + 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 matchingShapeContainerBuilderoverloads let a layer declared earlier draw on top of layers declared later. The layout compiler stable-sorts layers before render; equalzIndexkeeps source order.
DocumentTableCell.rowSpan(int)mirrors the existingcolSpan(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 benullto skip painting that parity. Existing entries in therowStylesmap (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 forheader(...)so authors writingheaderRow(...).row(...).totalRow(...)keep a parallel vocabulary.
InvoiceTemplateV2is the cinematic invoice counterpart toInvoiceTemplateV1. Two constructors: the no-arg form picksBusinessTheme.modern(), the one-argInvoiceTemplateV2(BusinessTheme)accepts any theme. HerosoftPanelcarrying 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 oldaddLine(horizontal=innerWidth)separators around section banners, andsoftPanel(...)collapses thepadding(asymmetric) + fillColor(...)cascade. InvoiceTemplateV1andProposalTemplateV1continue to ship side-by-side. Authors who want the cinematic look opt in by switching the type.
LayerStackBuilderexposes nine alignment shortcuts (topLeft,topCenter,topRight,centerLeft,center,centerRight,bottomLeft,bottomCenter,bottomRight) on top ofback/centerso authors do not need to remember the fullLayerAlignenum.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 raisingIllegalStateExceptionlater.DocumentDsl.richText(Consumer<RichText>)is a new callback entry point that builds aRichTextrun sequence in one fluent call.
- 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 existingAtomicNodeTooLargeExceptionwith the offending semantic name. TableLayoutSupportreplaces the per-rowcolSpan-sum check with a unified cell-grid pre-pass driven by an occupancy mask. The newbuildLogicalRows(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 ofcompileNodeInFixedSlotcompute a stableiterationOrderpermutation viastableZIndexOrder(...)before iterating the layer list. Stable on ties → equalzIndexkeeps source order.BuiltInNodeDefinitions.PreparedStackLayoutgains a fourth listzIndices: List<Integer>populated by bothShapeContainerDefinitionandLayerStackDefinition.- 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 commonThemeinterface that loses CV-specific vocabulary).
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@RestControllersnippet 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 productionLayoutSnapshotAssertions.assertMatches(document, "...")helper for in-test usage.WeeklyScheduleFileExamplerewritten to delegate to a new reusableexamples/support/WeeklyScheduleRenderer. The renderer's typed surface —JobTitleenum,StaffMember/DayPlan/Shiftrecords, 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 aLocalDate 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.
- 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
under
assets/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 underassets/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.mdgains 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.
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.
- 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, theeveryTransformBeginInArbitraryDocumentHasMatchingEndOnSamePagearchitecture-guard test) - per-layer z-index ordering and stable-tie behaviour
(
ShapeContainerZIndexDemoTestplus the two zIndex cases onShapeContainerBuilderTest) - 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
(
RowBuilder.gap(double)is deprecated in favour ofspacing(double). The deprecated alias still compiles; CV templates and runnable examples were migrated.RowBuilder.add(node)now throwsIllegalArgumentExceptioneagerly. Tests that asserted the deferredIllegalStateExceptioninbuild()must switch their expectation.- All other v1.4 record signatures stay backward-compatible:
LayerStackNode.Layer,ShapeContainerNode,TableNode,DocumentTableCell,TableResolvedCell, andBuiltInNodeDefinitions.PreparedStackLayoutship new canonical constructors and preserve every existing constructor as a back- compat shim that defaults the new fields.InvoiceTemplateV1andProposalTemplateV1ship 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.
- README rewrite for v1.4.0 dropped three structural sections (
## Table component,## Line primitive,## Architecture at a glance) that theDocumentationCoverageTestguards baseline. CI flagged the regression on themainbranch; v1.4.1 restores the sections (the table snippet now also points readers to the new column-span feature), keeps the canonical-DSL anti-patterns out of the snippets, and moves the architecture mermaid diagram back into its dedicated section.
examples/src/main/java/com/demcha/examples/GenerateAllExamples.javanow wiresCinematicProposalFileExample.generate()into the orchestrator, so the runnable examples module produces all seven fixtures (includingproject-proposal-cinematic.pdf) used by the README visual previews.
This is a documentation-only patch release. There are no public API changes; v1.4.0 consumers can upgrade with no code changes.
v1.4 closes the visual-design gap that the previous releases left open. Tables can now span columns, layers can stack on top of each other, sections and pages carry semantic backgrounds, paragraphs accept fluent rich text, and the whole look-and-feel can be parametrised through a single BusinessTheme. The release also lands the visual-regression scaffolding required to keep README screenshots stable across refactors.
DocumentTableCellis now a 3-field record (lines,style,colSpan). The newcolSpan(int)factory pluswithColSpan(...)onTableCellContentlet one cell occupy several columns; sum-of-spans-per-row is validated byTableLayoutSupport. Border ownership and natural-width distribution understand spans (extra width is shared acrossautocolumns inside the span; an all-fixed span throws when it cannot fit). Renderer code is unchanged — spanned cells emit a singleTableResolvedCellwith the merged width.- new
LayerStackNode+LayerAlignprimitive composes children inside the same bounding box, in source order (first behind, last in front). Each layer carries one of nine alignments (TOP_LEFT … BOTTOM_RIGHT). Pagination is atomic. Backed by a newAxis.STACKinCompositeLayoutSpecand acompileStackedLayerbranch inLayoutCompiler. DSL surface:LayerStackBuilderwithback(...),center(...),layer(node, align). DocumentSession.pageBackground(DocumentColor | Color)(and the matchingGraphCompose.DocumentBuildersetter) injects a full-canvasShapeFragmentPayloadat the start of every page. Combine withLayerStackNodefor cinematic hero pages without any backend changes.AbstractFlowBuildergains semantic shortcuts on every flow / section / module:band(color),softPanel(color)/softPanel(color, radius, padding), andaccentLeft / accentRight / accentTop / accentBottom(color, width). They reuse the existingfillColor,cornerRadius,padding, andDocumentBordersplumbing — the new methods are sugar for designer-style flows.RichTextfluent builder (document.dsl.RichText) plusParagraphBuilder.rich(...)/AbstractFlowBuilder.addRich(...)cover theStatus: Pendinglabel/value pattern in one expression:RichText.text("Status: ").bold("Pending").color("…", red).accent("…", brand). Includesplain / bold / italic / boldItalic / underline / strikethrough / color / accent / size / style / link / append.
- new
com.demcha.compose.document.themepackage — entirely on top of public document-level types, no engine leaks.DocumentPalette— primary / accent / surface / surfaceMuted / textPrimary / textMuted / ruleSpacingScale— five-stepxs / sm / md / lg / xlwith monotonicity validation andinsetsXs() … insetsXl()helpersTextScale—h1 / h2 / h3 / body / caption / label / accentresolved stylesTablePreset—defaultCellStyle / headerStyle / totalRowStyle / zebraStyleBusinessTheme— composes the four scales plus an optional page background, with three built-in presets (classic(),modern()cream paper + teal,executive()slate panels with Times-Roman headings) and immutablewithName / withPageBackgroundforks
com.demcha.testing.visual.ImageDiff— pixel-by-pixel comparison with per-channel tolerance and a red/grey diff image.com.demcha.testing.visual.PdfVisualRegression— renders PDF bytes to one PNG per page viaPdfRenderBridgeand compares against baselines undersrc/test/resources/visual-baselines. Approve mode (-Dgraphcompose.visual.approve=trueorGRAPHCOMPOSE_VISUAL_APPROVE=true) writes new baselines; comparison failures dropactual.pnganddiff.pngnext to the baseline for inspection.- 41 new tests across the cinematic surfaces (
TableColSpanIntegrationTest,TableBuilderColSpanTest,LayerStackBuilderTest,PageBackgroundTest,SectionPresetTest,RichTextTest,BusinessThemeTest,PdfVisualRegressionTest). Total green count: 525.
CompositeLayoutSpec.Axis.STACKjoinsVERTICALandHORIZONTAL. The compiler dispatchesSTACKtocompileStackedLayer, which positions each child inside the stack box via per-layer alignment offsets and shares the samecompileNodeInFixedSlotplumbing rows already use.- table layout (
TableLayoutSupport, test-sideTableBuilder) was rewritten around a "logical cell" model: each authored cell is oneLogicalCell(startColumn, colSpan, content)resolved against astylesGrid[row][col]— the grid keeps existing border-ownership logic intact while letting render code keep emitting oneTableResolvedCellper logical cell. DocumentSession.layoutGraph()now wrapscompiler.compile(...)withwithPageBackgrounds(...)so backends never need to know about the page-background option — they just iterate fragments as usual.
- Cinematic features have negligible overhead: page-background injection is a single fragment per page; column spans, layer stacks, and themes do not change the number of emitted fragments. End-to-end template latency stays in the same envelope as v1.3 once JIT is warm.
- Full benchmark surface is now published in the README:
current-speed(full profile) latency + per-stage breakdown, parallel throughput on the invoice template (1→8 threads),scalabilitysuite (1→16 threads, 13.8× speedup at 16), 50-threadstresstest (5,000 docs, 0 errors), and thecomparativetable against iText 5 and JasperReports.
- README rewritten around the cinematic v1.4 narrative: new sections for column spans, layer stacks, page background + section presets, rich text DSL, business themes, the visual-regression workflow, "Extending GraphCompose" guidance, and a refreshed Performance section sourced from
scripts/run-benchmarks.ps1.
DocumentSessionnow exposes ergonomic mutators for document-level PDF chrome:metadata(...),watermark(...),protect(...),header(...),footer(...), andclearHeadersAndFooters(). Convenience entrypoints (buildPdf,writePdf,toPdfBytes) honour these options without having to build aPdfFixedLayoutBackendmanually- new horizontal layout primitive:
addRow(...)on flows, sections, and modules creates aRowNodethat arranges atomic children left-to-right with optionalweights(...)andgap(...). Rows are atomic blocks from the paginator's perspective DocumentBordersvalue type plusborders(...)on flows, sections, modules, and rows let you describe per-side strokes (top / right / bottom / left). Per-side borders override the uniformstroke(...)settingParagraphBuilder.autoSize(maxSize, minSize)/autoSize(DocumentTextAutoSize)searches for the largest font size that fits the paragraph on a single line within the resolved inner width- new
addLink(text, uri)andaddLink(text, DocumentLinkOptions)shortcuts onAbstractFlowBuilderfor the common single-link case (was previously only available as a paragraph inline run) - backend-neutral output options under
com.demcha.compose.document.output(DocumentMetadata,DocumentWatermark,DocumentProtection,DocumentHeaderFooter, aggregated byDocumentOutputOptions). PDF and DOCX backends translate them; session-level metadata propagates to DOCX core properties as well as the PDF backend DocxSemanticBackendis now a functional Apache POI based backend that returns DOCX bytes (was a manifest-only skeleton); supports paragraphs, tables, images, spacers, page breaks, and document-level page geometry. Apache POI is declared optional, so consumers that only render PDFs do not pay the dependency cost
PageBreaker.paginationPrioritypre-computes(y, depth)keys and usesUUID.compareTofor tie-breaks (no per-compare string allocation). Old comparator allocated a 36-character UUID string for every priority queue compareEntity.getComponentandEntity.requireno longer issue per-call debug logging (even guardedisDebugEnabledcalls cost a volatile read on Logback)- table layout helpers (
resolveTableLayout,sliceTablePreparedNode, ~350 lines) extracted intoTableLayoutSupportfor clarity - end-to-end template rendering is 19–30 % faster than v1.2.0 on the canonical benchmark suite (
invoice-template: 25.77 → 10.77 ms avg;cv-template: 17.89 → 6.50 ms avg;proposal-template: 24.77 → 13.44 ms avg)
CurrentSpeedBenchmarksmoke profile bumped from 2 / 5 → 30 / 100 warmup / measurement iterations so the JIT reaches a steady state and percentiles are statistically meaningful- percentile calculation now uses linear interpolation between order statistics (
rank = (n-1) * p) so p95 no longer collapses to max at small sample counts System.gc()plus a 50 ms sleep separates warmup from measurement, dropping run-to-run variance from 10–25 % to 2–5 %peakHeapMbreports the heap delta over the post-warmup baseline rather than absolute used heap- a per-stage breakdown table (
compose / layout / render / total) prints alongside the latency table so consumers can attribute regressions to engine layout vs PDFBox serialization - smoke gate thresholds tightened from 800–2600 ms (effectively a no-op) to 8–100 ms (~3× the observed avg) — still safe against CI machine variance, now catches ≥50 % regressions
- the
ComparativeBenchmarkconsole table no longer wraps when library names exceed 20 characters
CompositeLayoutSpeccarries an explicitAxis(vertical / horizontal) and optional per-child weights; the layout compiler dispatches to a dedicated horizontal-row code path forRowNodeShapeFragmentPayloadcarries an optionalSideBorderspayload;PdfShapeFragmentRenderHandlerdraws each configured side stroke independently of the uniform rectangle strokeSemanticExportContextcarriesDocumentOutputOptionsso semantic backends (DOCX, future PPTX) can apply metadata / chrome configured at the session level- the unused engine
Buttonrenderable and theButtonBuildertest-support factory entry were removed - guide-line overlays now compute owner bounds across sub-fragments (e.g. table rows) and paint margin / padding once around the entire owning node instead of stacking dashed rectangles inside every row
docs/canonical-legacy-parity.mdis updated to reflect the v1.3 capabilities (rows, per-side borders, auto-size text, DOCX export)docs/benchmarks.mddocuments the new smoke profile defaults, the GC stabilization point, the linear-interpolation percentile rule, and the stage-breakdown tableCONTRIBUTING.mdrepository map and package list now describe the canonical functional layout (document.layout,document.backend,document.output) alongside the legacy ECS engine
- the current canonical API cleanup is being released as v1.2.0 to match the project's early maturity while still making
GraphCompose.document(...) -> DocumentSession -> DocumentDslthe preferred authoring path - Maven coordinates are
io.github.demchaav:graphcompose:1.2.0; JitPack consumers continue to usecom.github.demchaav:GraphCompose:v1.2.0 - consumers on
v1.1.xshould adopt the canonicalGraphCompose.document(...)session-first path; the planneddocs/migration-v1-1-to-v1-2.mdwas never written and the canonical surface has stabilised since
DocumentSessionis now anAutoCloseablelifecycle owner:close()is idempotent, and authoring/rendering methods on a closed session fail fast withIllegalStateExceptioninstead of returning broken state- empty document rendering (
writePdf/toPdfBytes/buildPdf) now throws a domain-specificIllegalStateExceptioninstead of producing a zero-byte / zero-page PDF; add at least one root before rendering DocumentPageSizeis the public page-size value;GraphCompose.document(...).pageSize(PDRectangle)was removed from the canonical APIDocumentSession#margin(Margin)andGraphCompose.DocumentBuilder#margin(Margin)were removed from the canonical API; useDocumentInsetsormargin(top, right, bottom, left)to keep authoring renderer-neutral- PDF-specific metadata, protection, watermark, and header/footer options moved behind
PdfFixedLayoutBackend.builder()instead of the canonicalGraphCompose/DocumentSessionsurface GraphCompose.document(...).guideLines(true)andDocumentSession.guideLines(true)now enable debug guide-line overlays forbuildPdf,writePdf, andtoPdfBytesconvenience outputDocumentSession.layoutSnapshot()now returns public renderer-neutralcom.demcha.compose.document.snapshot.*DTOs instead of engine debug typesBoxConstraints.natural(width)is now the canonical natural-measurement factory;unboundedHeight(width)remains as a compatibility alias- the public font registry no longer exposes the unadvertised
getPdfFont(...)bridge; backend code resolves typed fonts throughgetFont(..., PdfFont.class)
PublicApiNoEngineLeakTestbaselines the inventory ofcom.demcha.compose.engine.*imports allowed in the public API surface — any new leak fails the buildSemanticLayerNoPdfBoxDependencyTestkeepsdocument.node.*free of direct PDFBox imports and pins the remainingbackend.fixed.pdf.options.*references for Phase 3 cleanupPdfBackendIsolationGuardTestkeeps PDFBox out of canonical API, DSL, semantic nodes, layout, snapshots, and non-PDF backend contracts
PaginationEdgeCaseTestadds focused regressions for exact-fit content, near-boundary float handling, leading / trailing page breaks, oversized atomic images, too-tall table rows, module splits with PDF chrome, and nested sections that paginate while preserving margin and padding
- new
docs/migration-v1-1-to-v1-2.mdoutlines the move from older v1.1 usage patterns to the canonical session-first API - new
docs/v1.2-roadmap.mdtracks the remaining stabilization work for the v1.2 release polish window docs/release-process.mdnow describes the current JitPack-first 1.x release flow and runnable examples verification- user-facing docs now describe debug guide-line overlays through
GraphCompose.document(...).guideLines(true)/DocumentSession.guideLines(true)and call out JitPack tag-cache handling during release verification
- shifted the public built-in template narrative to
compose(DocumentSession, ...) - added document-level PDF features for richer real-world output
- moved the engine further away from PDF-centric internals through backend-neutral composition and render-handler seams
- strengthened architecture guard rails for template scene builders
- expanded visual testing and benchmark tooling for day-to-day development
- canonical
DocumentSessioncontract as the primary composition seam - layout snapshot extraction and JSON-based regression coverage for resolved document geometry
- runnable
examples/module for CV, cover letter, invoice, proposal, and weekly schedule generation - new built-in business templates and data models for invoice, proposal, and weekly schedule documents
- barcode support with QR, Code 128, and EAN-13 builders
- watermark support
- configurable headers and footers with page numbers and separators
- PDF bookmarks / outline generation
- document metadata support
- PDF protection hooks
- explicit page-break and divider builders
- visual showcase render tests for barcodes, QR codes, pagination, and document chrome
- current-speed benchmark suite
- benchmark JSON/CSV export and diff tooling
- one-command benchmark runner: scripts/run-benchmarks.ps1
- bumped the library release to
v1.1.0 - updated README installation snippets to the new release version
- documented built-in templates as compose-first by default
- refreshed README visuals to show barcode/QR and compose-first template output
- added release-facing notes for the experimental live preview dev tool in test scope
- refreshed release documentation to point contributors at visual tests and benchmark workflows
- engine-side text measurement and rendering dispatch are now more explicitly decoupled from PDFBox-specific implementation details
- added template boundary guard coverage so
*SceneBuilderclasses stay free of backend-specific PDFBox types - split architecture/documentation guards into a dedicated CI job that can be required independently in branch protection
- older tagged JitPack releases remain usable as long as consumers pin a specific version such as
v1.0.3 - deprecated
render(...)template adapters remain available for compatibility, but new docs and examples now prefercompose(...)