Releases: DemchaAV/GraphCompose
v1.6.4 — Boxed Sections parser fix + structured WorkHistory/Education blocks
Bug fix + structured-block patch. Adds two new public Block types —
WorkHistoryBlock and EducationBlock — that let template authors
declare work-history and education entries with explicit (title,
organisation, date, description) / (degree, institution, year,
details) fields instead of relying on the legacy
MultiParagraphBlock pipe-separated string parser. Also closes a
Boxed Sections layout defect that bundled the date and description
into the right-aligned date column for any author-supplied line that
used an em-dash (" — "), en-dash (" – "), or contained
prose-shaped content the parser misread as a date. No public API
break — the sealed Block permit list grows from six to eight,
existing MultiParagraphBlock work-history strings continue to
parse, and the deprecated parser path stays in place for backward
compatibility.
Templates — new structured blocks
WorkHistoryBlock. New public record block carrying a list of
Item(title, organisation, date, description)entries. The
BoxedSectionspreset renders each item as a structured row:
title bold on the left, date right-aligned on the same row,
organisation italic on the next line under the title, and
description as a full-width paragraph beneath. Other presets fall
back to a single concatenated paragraph per item. Authors who use
WorkHistoryBlockbypass the legacy
BoxedSections#parseWorkEntryheuristic parser entirely.EducationBlock. New public record block carrying a list of
Item(degree, institution, year, details)entries. Renders with
the same structured layout asWorkHistoryBlock(degree bold
left, year right, institution italic, details paragraph) so
Education & Certifications sections visually match Professional
Experience.- Sample data migrated.
ExampleDataFactory.sampleCvSpecV2now
usesWorkHistoryBlockfor Professional Experience and
EducationBlockfor Education & Certifications. The legacy
MultiParagraphBlockpattern remains supported and is exercised
byPresetLayoutSnapshotTest/PresetVisualParityTestto lock
the backward-compat path.
Templates — parser robustness (legacy path)
parseWorkEntryaccepts em-dash and en-dash. Used to split
the post-pipe segment on ASCII" - "only; now tries" — ",
" – ", and" - "in order, mirroringsplitHeading. Authors
who typed"*2024-Present* — Led reusable document flows."saw
the whole tail collapse into the date column — this no longer
happens.parseWorkEntryrejects prose dressed up as a date. The
looselooksLikeDatecheck accepted any string containing a
year and a hyphen anywhere, which caused education lines like
"... | 2019. First-class honours. Specialisation ..."to
parse as work entries (the hyphen inside"First-class"was
enough to satisfy the heuristic). Parser now rejects post-pipe
segments that contain sentence-ending punctuation (.,:,
;) when no explicit date / description separator was found,
letting these lines fall back to plain paragraph rendering.
Marked@Deprecatedwith a@deprecatedJavadoc pointing
callers toWorkHistoryBlock/EducationBlock.parseProjectItempicks up the same em-dash / en-dash /
ASCII separator set so future Project items typed with em-dash
don't regress into "title only" rendering.
Tests
BlockTest.blockSealingPermitsAllEightVariantsupdated for the
two new permitted block types.PresetVisualGalleryTest.sampleSpecmigrated to
WorkHistoryBlockso the visible "primary example" exercises the
new structured shape.PresetLayoutSnapshotTestintentionally retained on
MultiParagraphBlockto lock the legacy parser's behaviour.
v1.6.3 — Tight PDF link rects + preserved whitespace + Projects layout fix
Bug fix patch. Closes two independent hyperlink clickable-area
defects that surfaced on CV gallery presets and made the LinkedIn /
GitHub contact rows hijack each other's clicks (paragraph-level
link path) or drift past their visible text (span-level link path
through multi-space separators). No public API change — engine,
DSL, themes, templates, and backend records all stay
source-compatible with v1.6.2.
Engine
- Paragraph-level link annotations now hug rendered text.
PdfFixedLayoutBackendused to emit a paragraph'slinkOptions
as a single rectangle covering the entire fragment box
(fragment.x()+fragment.width()), ignoringTextAlign.RIGHT
/TextAlign.CENTER. Stacked right-aligned contact paragraphs
(e.g. one per LinkedIn / GitHub icon row in Timeline Minimal /
Sidebar Portrait / Monogram Sidebar) therefore produced
full-column-wide rects that overlapped the empty alignment gap of
neighbouring rows — hovering over GitHub clicked the LinkedIn row.
The backend now emits one per-line rect tight toline.width()
positioned at the alignment-awarelineX, matching how
inline-span links already worked. Span-level link emission, table
/ shape / barcode payload links, and bookmark anchoring are
unchanged. - Glyph sanitizer preserves all author whitespace.
PdfFont.sanitizeForRenderused to collapse any run of consecutive
spaces into a single space, both for whitespace-only tokens (the
" "halves of a" | "separator) and for inter-word gaps
in spaced-caps strings (spacedUpper("ARTEM DEMCHYSHYN")produces
"A R T E M D E M C H Y S H Y N"with deliberate triple-spaces
between words). The collapse shrank the rendered glyph stream
under measurement, drifting inline-link rectangles ~8pt per
" | "separator past their visible labels and visually
merging spaced-caps titles back into a single run ("A R T E M D E M C H Y S H Y N"— no word boundary). The sanitizer no longer
collapses adjacent spaces; newlines / NBSP / non-tab control
characters still resolve to a single space each, but author
whitespace is now preserved verbatim so wrap geometry,
link-rectangle emission, andshowText(...)all see the same
string. Layout snapshot baselines for five CV presets and one
nested-list document widened to reflect the recovered whitespace —
the deliberate visual change is the bug fix.
Templates
- Boxed Sections projects render as title + indented description.
The "Projects" module now renders each bullet-list or
IndentedBlockitem as two stacked paragraphs — bullet plus bold
project name (with an optional tech-stack chunk in parentheses) on
the first line, then a hanging-indented description below aligned
to the project name (not the bullet). The previous single-line
rendering ran the project name and description together. Bullet
marker, hanging-indent, and surrounding modules are unchanged.
Example data inExampleDataFactory.sampleCvSpecV2and
PresetVisualGalleryTestnow ships tech-stack chunks ("Java 21, PDFBox, Maven, JMH") so the gallery PDFs reflect the new layout.
Tests
- New regression in
PdfFixedLayoutBackendFeaturesTest—
shouldTightlyHugRightAlignedParagraphLinkRectangles— stacks
three right-aligned link paragraphs and asserts each clickable
rect hugs its rendered label width (≤ 150pt), sits flush against
the inner right margin, and does not overlap the Y-band of
neighbouring rows. - New regression in
PdfFixedLayoutBackendFeaturesTest—
shouldKeepCenteredInlineLinkRectanglesAlignedAcrossMultiSpaceSeparators
— renders a centered contact line built with" | "separators
and asserts the three resulting link rectangles preserve
left-to-right order with non-overlapping X ranges and a sane
per-separator gap (5..40pt), pinning the bug where collapsed
whitespace pushed later rects past the line. - New regression in
PdfFontSanitizerTest—
sanitizeForRender_preservesWhitespaceOnlyTokensVerbatim— pins
the whitespace-only short-circuit so render width stays in
lockstep withgetTextWidthfor tokenised contact-line
separators.
v1.6.2 — Sorry, the PDF was crashing on emoji
v1.6.2 — Sorry, the PDF was crashing on emoji
Patch release. Closes four engine bugs I found while building yet
another CV template and assuming "well, this one definitely works."
It did not.
Zero breaking changes. If you're on v1.6.1, upgrading is safe.
Honest.
What's fixed
🔡 Any Unicode glyph no longer kills the document.
Before: a single ● in a paragraph, watermark, or footer would
make PDFBox throw IllegalArgumentException deep inside showText
and the whole render went up in smoke. I was convinced this was a
known limitation. It was a bug. PdfFont.sanitizeForRender already
existed in the codebase, nobody was calling it. Now everybody is.
Emoji, arrows, copyright marks — substituted to ? with one WARN
per unique (font, codepoint). No crashes. No surprises.
📏 842pt no longer "too large" for A4.
DocumentPageSize.A4.height() is 841.88977pt. Exactly. Per the
spec. If you are a regular human being who writes 842 because A4
is 842pt tall (as everyone learned) — you used to get
requires outer height 842.0 but page capacity is 841.88977. A
0.11pt overflow. That's 0.04 mm. The capacity check now has a 0.5pt
tolerance — invisible to the eye, but enough to stop fainting at
round-trip rounding.
🎨 Row inside LayerStack is now legal.
Want a dark top band on your CV with a sidebar+main row underneath?
Obviously through a LayerStack (layer 0 = band, layer 1 = row of
two columns). Obviously the validator rejected this, because it
couldn't tell a parent-row-band from a parent-layer-rectangle. It
can now. Row inside a layer works. Row inside another row still
does not — that one is a real composition conflict.
🛠️ Exception messages now tell you what to do.
Before: cannot contain a nested horizontal row; use a section column instead. Sounds like a lecture from your compiler. After:
Wrap the inner row in a LayerStack layer (allowed since v1.6.2), or stack horizontal content as sections inside a vertical column.
Meaning: try this thing. Breakthrough, I know.
Tests
828+ existing tests stay green, plus:
PdfFontSanitizerTest— 8 sanitizer casesLayerStackRowCompositionTest— 3 positive + 1 negativePaginationEdgeCaseTest— 2 new boundary cases- Three visual demo PDFs under
target/visual-tests/:
glyph-fallback/,page-capacity/,layer-stack/ - Bonus:
DevelopTestscratch class for manual experimentation
Meta
- Maintainer email in
pom.xmlis fixed. Nobody was emailing the
old one, which was convenient. If you ever wanted to reach me —
it now actually goes somewhere: demchishynartem@gmail.com. - README links to the companion experiment
graphcompose-ai-flow
— where I'm exploring AI-assisted document authoring on top of
GraphCompose. Independent codebase, separate lifecycle, broken
things welcome. Check it out if that sounds fun.
Install
<dependency>
<groupId>com.github.DemchaAV</groupId>
<artifactId>GraphCompose</artifactId>
<version>v1.6.2</version>
</dependency>implementation("com.github.demchaav:GraphCompose:v1.6.2")What's next — v1.7
DX helpers on top of an engine that no longer crashes: heading
levels (h1/h2/h3), addFieldRow(label, value),
page.twoColumn(...), addHeadingBar(...), inline shape runs
(ratingDots, badge). All additive. Plan tracked privately;
follow develop.
Full diff: v1.6.1...v1.6.2
GraphCompose v1.6.1 — Java 17 baseline + dependency refresh
For users
GraphCompose v1.6.1 is a 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 (incl. CVE pass + ByteBuddy/Mockito update so Mockito works on JDK 25).
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).
Toolchain
- 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 / 21 / 25 in matrix. - 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.
Verified
- CI green on Temurin JDK 17 / 21 / 25 in parallel matrix.
- 819 / 0 / 0 / 0 canonical test suite green on every JDK.
- 26 runnable examples regenerate cleanly.
- Engine source has zero remaining Java 21–only constructs (
getFirst,getLast,threadId, switch type patterns, switch deconstruction).
Quick start
Maven:
<repositories>
<repository><id>jitpack.io</id><url>https://jitpack.io</url></repository>
</repositories>
<dependency>
<groupId>com.github.DemchaAV</groupId>
<artifactId>GraphCompose</artifactId>
<version>v1.6.1</version>
</dependency>Gradle (Kotlin DSL):
repositories { maven("https://jitpack.io") }
dependencies { implementation("com.github.demchaav:GraphCompose:v1.6.1") }Looking ahead
Maven Central distribution (#7) remains queued for the upcoming v1.7.0, alongside the JMH benchmark migration. v1.6.1 stays on JitPack.
Links
- Live showcase — install snippet, every example with a generated PDF preview, template gallery, feature examples.
- Templates v2 landing — 14 CV + 14 paired cover-letter presets.
- Migration v1.5 → v1.6 — for callers still on v1.5 (engine source-compatible to v1.6.x).
- CHANGELOG.md — full technical changelog.
Author intent, not coordinates.
GraphCompose v1.6.0 — expressive release
For users
v1.6.0 — "expressive" — makes GraphCompose more expressive without breaking your engine code:
- Nested lists are now easier to compose —
ListBuilder.addItem(label, Consumer)opens a child scope, and a per-depth marker cascade (•→◦→▪→·) handles the visual hierarchy. Mixed flat / nested authoring preserves source order. - Table cells can hold any composable node —
DocumentTableCell.node(...)accepts paragraphs, nested lists, sub-tables, layer stacks. Two-pass measurement preserves the row-by-row pagination contract. - Canvas layers support pixel-precise
(x, y)placement —CanvasLayerNodeis the controlled free-canvas primitive for when you do need coordinates, withClipPolicy.CLIP_BOUNDSclipping and atomic pagination. - Templates v2 ships 14 CV presets and 14 paired cover-letter presets, theme-driven via
BusinessTheme, with one-linercreate(theme)factories. Inline markdown, active hyperlinks, slot-based multi-column layouts. The Templates v2 visual pipeline is hardened with pixel-diff parity checks against v1 reference renders to catch visual regressions on every build. - Architecture hardening —
@InternalAPI stability marker on engine internals, publicPdfFragmentRenderHandlerSPI for custom render handlers,DocumentRenderingExceptionwrapping the convenience render path sobuildPdf/writePdf/toPdfBytesno longer declarethrows Exception, documented thread-safety contract.
Engine source-compatibility with v1.5 is preserved for unmodified callers. Every public record that grew a new field ships back-compat constructors that default the new value, so v1.5 callers compile and behave unchanged.
Breaking change
Templates v2 replaces the legacy CV / cover-letter template classes. Legacy classes (CvTemplateV1, NordicCleanCvTemplate, MonogramSidebarCvTemplate, …) are deleted, not deprecated. Anyone constructing those classes must switch to the matching v2 preset's create(BusinessTheme) factory. The full v1 → v2 mapping (every old class → its v2 replacement, with before/after code) is documented in docs/migration-v1-5-to-v1-6.md.
Quick start
Maven:
<repositories>
<repository><id>jitpack.io</id><url>https://jitpack.io</url></repository>
</repositories>
<dependency>
<groupId>com.github.DemchaAV</groupId>
<artifactId>GraphCompose</artifactId>
<version>v1.6.0</version>
</dependency>Gradle (Kotlin DSL):
repositories { maven("https://jitpack.io") }
dependencies { implementation("com.github.demchaav:GraphCompose:v1.6.0") }Distribution status — currently published via JitPack. Maven Central is planned for v1.7 (tracking issue #7).
What's verified
- 819 / 0 / 0 / 0 — full canonical test suite green via
mvnw verify. - 26 runnable examples regenerate cleanly via
GenerateAllExamplesand ship as a CI artifact on every build. - 28 layout-snapshot baselines + 29 pixel-diff visual baselines cover every CV / cover-letter preset. The parity gate runs on every CI build with a calibrated
mismatchedPixelBudgetfor cross-platform PDFBox font drift.
Links
- Live showcase — install snippet, every example with a generated PDF preview, template gallery, feature examples.
- Templates v2 landing — preset gallery, four-layer architecture explanation, quick-start.
- Migration v1.5 → v1.6 — v1 → v2 template upgrade table.
- CHANGELOG.md — full technical changelog, including architecture-hardening details, package-level changes, and ADRs 0011-0014.
Author intent, not coordinates.
GraphCompose v1.5.1
v1.5.1 — 2026-05-05
Dependencies
- 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.
Tooling
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.
GraphCompose v1.5.0 — the intuitive release
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...
GraphCompose v1.4.1 — README guards hotfix
Documentation
- 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.
Tooling
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.
GraphCompose v1.4.0 — cinematic document engine
Headline — "cinematic document engine"
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.
Public API — semantic primitives
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.
Public API — design tokens
- 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
Testing infrastructure
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.
Architecture
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.
Performance
- 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.
Documentation
- 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.
GraphCompose v1.1.0
Highlights
- compose-first built-in template usage is now the documented default
- backend-neutral
DocumentComposerand handler-driven rendering make the engine less PDF-centric internally - layout snapshot testing is now part of the regression workflow for pagination and geometry changes
- runnable examples now cover CV, cover letter, invoice, proposal, and weekly schedule generation
- document-level PDF features now include QR/barcodes, watermarks, headers/footers, bookmarks, metadata, protection, explicit page breaks, and dividers
- visual showcase tests make pagination, document chrome, and barcode output easier to inspect
- benchmark tooling now includes current-speed, comparative, and diffable JSON/CSV reports
- an experimental live preview dev tool is available in test scope for fast template iteration
Added
- 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
- benchmark export and diff tooling
- one-command benchmark runner
Compatibility
- older tagged JitPack releases such as
v1.0.3remain usable - deprecated
render(...)template adapters are still available for compatibility - new docs and examples now prefer
compose(...)