Thanks for helping improve GraphCompose.
Read these files first:
- README.md
- docs/architecture.md
- docs/implementation-guide.md
- docs/benchmarks.md when you touch benchmark tooling, render hot paths, layout hot paths, or performance-facing docs
They explain the current public surface, the engine/template split, and the recommended extension points.
GraphCompose targets Java 17+ as of v1.6.1. CI runs the full test suite against Temurin JDK 17 / 21 / 25 in parallel matrix, so JDK-incompatibility regressions fail the PR immediately.
When writing new code, avoid Java 21+ APIs and language constructs that don't exist in 17:
List.getFirst()/List.getLast()→list.get(0)/list.get(list.size() - 1)Thread.threadId()→Thread.getId()switchwith type patterns (case Foo f -> …) →instanceofif-else chainsswitchwith deconstruction patterns (case Foo(Bar b) -> …) →instanceof Foo f+f.bar()case null, default ->→ explicitif (x == null) return …;early returnList.reversed()→Collections.reverse(new ArrayList<>(list))
- The blocking validation gate for repository work is
./mvnw -B -ntp clean verify. - Run the guard-focused suite with
./mvnw -B -ntp "-Dtest=EnginePdfBoundaryTest,CanonicalTemplateComposerPdfBoundaryTest,PdfRenderInterfaceGuardTest,PdfRenderingSystemECSDispatchTest,DocumentationCoverageTest,DocumentationExamplesTest,CanonicalSurfaceGuardTest,TemplateComposeApiTest" test. - Run a focused documentation sanity check with
./mvnw -B -ntp "-Dtest=DocumentationExamplesTest" test. - Run the local benchmark wrapper with
powershell -ExecutionPolicy Bypass -File .\scripts\run-benchmarks.ps1when you change performance-sensitive code or benchmark tooling.
GraphCompose follows a fork → feature branch → pull request flow. External contributions land on develop (the working branch); main is the public stable line and only accepts release merges from develop.
-
Fork the repository on GitHub and clone your fork locally.
-
Create a feature branch from
develop:git checkout develop git pull --ff-only origin develop git checkout -b feature/short-description
Use
feature/...for new functionality,fix/...for bug fixes, anddocs/...for documentation-only changes. Issue-prefixed names (42/fix/short-description) are also welcome — convenient when the branch closes a specific issue. -
Commit small, focused changes. Each commit message should describe the why, not just the what. Recent commits on
develop(Prepare v1.5.0 release,Align public docs with the canonical surface) are reasonable length and structure templates. -
Run the validation gate locally before opening a PR:
./mvnw -B -ntp clean verify
This runs the architecture-and-documentation guards plus the full test suite. The same gate runs in CI on every PR.
-
Push your feature branch to your fork and open a pull request against
developonDemchaAV/GraphCompose. Reference any related issue and describe the user-visible change in the PR body. -
CI runs automatically. Active jobs:
Architecture and Documentation Guards— fast canonical / engine-boundary guard tests, fail-first gateBuild and run tests (JDK 17),(JDK 21),(JDK 25)— fullmvnw verifyin parallel matrix across the supported JVMsExamples Generation Smoke Test— regenerates all 26 runnable examples and uploads the PDFs as a CI artifactPerformance Smoke Check— PR-only coarse benchmark to catch performance regressions
The PR cannot merge into a protected branch until all required checks are green.
-
Address review comments, then squash any fixup commits before merge. The maintainer merges through GitHub once review is complete.
main is protected:
- pull request required (no direct pushes)
- both CI status checks must pass before merge
- linear history is enforced (squash or rebase, no merge commits)
- force pushes and branch deletion are disabled
develop accepts feature-branch PRs from contributors. The maintainer may push directly to develop for solo-driven release prep work; external contributions still flow through PRs.
- Release prep lands on
develop— version bumps inpom.xml,examples/pom.xml, andbenchmarks/pom.xml; fresh CHANGELOG entry; migration guide for minor releases. README install snippets stay pinned to the previously published tag (e.g.v1.6.0) until JitPack confirms the new build, otherwise consumers copying the snippet during the publish window hit a 404. scripts/cut-release.ps1 -Version <X.Y.Z>automates the bump + CHANGELOG date + commit + tag + push fromdevelop. The maintainer fast-forwardsmainfromdevelopafter the tag lands (git push origin develop:main).- JitPack picks up the new tag automatically. After JitPack reports
BUILD SUCCESS, a separate post-release commit ondevelopflips the README install snippets to the new version. - GitHub Release is created with notes from the matching
CHANGELOG.mdsection.
See docs/release-process.md for the full checklist (audit gates, hotfix protocol, lessons learned).
src/main/java/com/demcha/compose/document/api,document.dsl,document.node,document.style,document.table,document.image,document.output,document.exceptions,document.snapshotPublic canonical authoring surface —DocumentSession, the DSL builders, semantic node records, public style values, table types, image types, backend-neutral output options (metadata / watermark / protection / header-footer), and snapshot DTOssrc/main/java/com/demcha/compose/document/layoutCanonical functional layout pipeline:LayoutCompiler,BuiltInNodeDefinitions,TableLayoutSupport,PreparedNode,PlacedFragmentsrc/main/java/com/demcha/compose/document/backend/fixed/pdfPDF backend:PdfFixedLayoutBackend, fragment handlers, and the option translators that bridge canonical types to PDFBoxsrc/main/java/com/demcha/compose/document/backend/semanticSemantic exporters:DocxSemanticBackend(Apache POI based),PptxSemanticBackend(manifest skeleton)src/main/java/com/demcha/compose/document/templates/*Built-in templates (CV, cover letter, invoice, proposal, weekly schedule), DTOs, themes, registries, and scene composition helperssrc/main/java/com/demcha/compose/engine/*Internal ECS engine kept for the legacy template path. Not part of the recommended public APIsrc/main/java/com/demcha/compose/fontPublic font registry,FontName, default fonts,FontShowcasesrc/test/java/com/demcha/documentation/*Examples used to keep README/documentation snippets honestsrc/test/java/com/demcha/compose/engine/integration/*End-to-end behaviour checks for the legacy ECS layout, pagination, and rendering pathssrc/test/java/com/demcha/compose/document/*Canonical API, DSL, layout, backend, and template testsassets/readme/*Screenshots used by the README
- Start with the smallest change that solves one problem.
- Keep structural cleanup separate from behavior changes whenever possible.
- If you touch public examples or screenshots, update the related docs in the same change.
- Run the smallest relevant tests while iterating, then run
./mvnw -B -ntp clean verifybefore opening a pull request. - For quick visual iteration on a template, run GraphComposeDevTool.java in test scope — it hot-reloads the rendered PDF as you edit your template source.
GraphCompose is split into a public canonical authoring surface
(com.demcha.compose.document.*) and an internal engine foundation
(com.demcha.compose.engine.*). New features land on the canonical
surface; the engine foundation stays an internal detail. The rules
below reflect that split.
Most contributions add a new public node, builder, style value, or template feature. The rules:
- DSL builders live in
com.demcha.compose.document.dsl. Implementation helpers belong indocument.dsl.internaland must not leak into the public surface. - Semantic node records live in
com.demcha.compose.document.node. When a new field is added later, ship a back-compat constructor that defaults the new field — seeShapeContainerNode,TableNode,LayerStackNode.Layer, the v1.5*Noderecords that gainedtransform, etc. - Public style / table / image / theme / output value types live under
document.style,document.table,document.image,document.theme, anddocument.output. They stay renderer-neutral — noorg.apache.pdfboximports. - Layout integration for a new node is a
NodeDefinition<MyNode>registered withNodeRegistry. SeeBuiltInNodeDefinitionsfor the established pattern. - Built-in templates in
...document.templates.builtinsstay thin public facades over reusable scene composers in...document.templates.support. Keep PDF-only setup in the document session/backend layer rather than inside template composers, and do not importPDDocument,PDPage,PDRectangle, or low-level PDF composer types into scene composer classes. - Public template contracts are compose-first: prefer
compose(DocumentSession, ...). New README snippets, runnable examples, and integration docs must showcompose(...)rather than the removed low-level PDF entry points.
These rules apply when you touch measurement, pagination, the render-pass session, or PDF render dispatch. Application code should not need any of them.
- Engine render markers implement backend-neutral
Render. Do not add backend-specific render interfaces back intoengine/components. - PDF rendering logic lives in
src/main/java/com/demcha/compose/engine/render/pdf/handlers/. Backend-only helper objects live incom.demcha.compose.engine.render.pdf.helpers, not incomponents/renderable. - Builders and layout code get text width and line metrics from
TextMeasurementSystem, not fromLayoutSystem -> RenderingSystem,PdfFont, or PDFBox objects. - Keep
src/main/java/com/demcha/compose/engine/components/*free oforg.apache.pdfboxandcom.demcha.compose.engine.render.pdfimports. - When you add a new render marker, register its handler in
PdfRenderingSystemECSand add or update dispatch coverage.
Keep the entity core thin:
Entitystays an identity-plus-components object with compatibility delegates. It is not a home for new layout math or pagination mutation rules.- Geometry reads live in
EntityBounds. Parent-container size and page-shift propagation live inParentContainerUpdater. Entity.bounding*andEntity.updateParentContainer*are deprecated compatibility wrappers; do not copy them into new code.- Render-order optimizations live in rendering helpers such as
EntityRenderOrder, not inEntity.
The rules above are enforced by tests:
- canonical surface guards: CanonicalSurfaceGuardTest.java, TemplateComposeApiTest.java, PublicApiNoEngineLeakTest.java, SemanticLayerNoPdfBoxDependencyTest.java, DocumentationExamplesTest.java, DocumentationCoverageTest.java
- engine internals guards: EnginePdfBoundaryTest.java, CanonicalTemplateComposerPdfBoundaryTest.java, PdfRenderInterfaceGuardTest.java, PdfRenderingSystemECSDispatchTest.java
If application code should be able to add a new visible thing to a document:
- Define a public record under
com.demcha.compose.document.nodewith a compact constructor that normalizes optional fields. Validate non-finite or negative dimensions where relevant. - Add a
NodeDefinition<MyNode>inBuiltInNodeDefinitions. Implementprepare(...)(measurement),paginationPolicy(...), andemitFragments(...). If your node should supportDocumentTransform, follow thewrapAtomicWithTransformpattern used byShapeNode,EllipseNode,LineNode,ImageNode, andBarcodeNode. - Add a public builder under
com.demcha.compose.document.dsl. InheritAbstractFlowBuilder<T, N>for the commonaddParagraph/addTable/addRow/softPanel/accent*surface; implementTransformable<T>if rotation / scale should apply. - Add convenience overloads on
AbstractFlowBuilderfor the "common case" if the new node has one worth a one-line shortcut (e.g.addCircle(diameter, fill)). - Add a
*Testcovering the builder contract plus a layout snapshot test (LayoutSnapshotAssertions) and a PDF render smoke test (PdfVisualRegression) when the visual output matters. - Update docs/recipes/ if the feature has a
copy-pasteable usage pattern, and add a runnable example under
examples/src/main/java/com/demcha/examples/with aGenerateAllExampleshook if it deserves a PDF preview.
Reference templates to copy:
ShapeContainerNode+ShapeContainerBuilder+ShapeContainerBuilderTest(composite + clip + transform)TableNode+TableBuilder+TableBuilderRowSpanTest/TableBuilderZebraAndTotalsTest/TableBuilderRepeatHeaderTest(multi-feature node)EllipseBuilder+EllipseNode+TransformableLeafBuildersTest(atomic leaf with transform)
- Constructor takes a
BusinessTheme(orCvThemefor CV templates). Provide a no-arg overload that picks a default theme. - Compose against
DocumentDsl— no PDF-specific imports. - Route every visible token through
theme.palette()/theme.text()/theme.spacing()/theme.table(). - Reference:
InvoiceTemplateV2,ProposalTemplateV2. Read docs/template-authoring.md before starting.
If you are extending the engine foundation itself (a new render marker, a new layout system, a new render-pass session):
- Decide first whether the feature belongs on the public surface as
a
DocumentNodeinstead. If yes, see "New public node" above and treat the engine work as plumbing, not as new public ECS surface. - For genuine engine primitives, add the engine content / style / layout component plus a backend-neutral renderable marker plus a backend-owned render handler.
- Marker rule of thumb:
- add
Expendableonly to parent-like boxes that should grow because of child content - add
Breakableonly to entities whose own content may continue across pages - do not treat
Expendableas a pagination flag
- add
For text-heavy primitives, also read:
If the primitive should be available to application developers,
expose it through DocumentDsl and a public DocumentNode, not a
low-level test harness.
Choose the smallest tests that match the change:
- For README or docs examples: DocumentationExamplesTest.java
- For engine/backend boundary changes: EnginePdfBoundaryTest.java PdfRenderInterfaceGuardTest.java
- For low-level test harness changes: ComponentBuilderTest.java
- For render-marker dispatch changes: PdfRenderingSystemECSDispatchTest.java
- For layout/positioning behavior: ComputedPositionTest.java
- For pagination and multi-page behavior: PageBreakerIntegrationTest.java
- For Templates v2 CV / cover-letter presets: PresetVisualParityTest.java (CV) PresetVisualParityTest.java (cover letter) PresetLayoutSnapshotTest.java
If a change affects public docs, examples, or screenshots, update those assets in the same PR so the repository stays internally consistent.
If a change affects resolved geometry, pagination, or ordering, prefer adding or updating a layout snapshot test as well. Snapshot coverage is debug-only and test-oriented: it should validate layout state without being wired into the normal production PDF pipeline.
- Preserve existing public Java class names and package paths unless a planned migration explicitly says otherwise.
- Avoid mixing cleanup, refactors, and behavior changes in one PR.
- When touching docs or examples, keep them aligned with the current public API and file layout.
- If a change affects resources, tests, or generated outputs, update the related references in the same PR.
- Prefer additive or backward-compatible changes when extending canonical DSL APIs or template contracts.
- If a rename or move could break imports, resource paths, or examples, either update every affected reference in the same change or leave it as a documented follow-up.
- Keep README.md aligned with the tested examples.
- Keep benchmark values clearly dated when they are refreshed.
- Keep
assets/readme/*screenshots consistent with the current render outputs. - If you add a new extension point or contribution pattern, update README.md, docs/architecture.md, and docs/implementation-guide.md as part of the same change.
- If you change benchmark flow, benchmark artifact layout, or diff selection rules, update README.md and docs/benchmarks.md in the same change.
- Visual PDF artifacts are grouped under
target/visual-tests/clean/*andtarget/visual-tests/guides/*so guide-line renders are easy to find separately from clean outputs.
The repository uses these normalized package roots:
com.demcha.compose—GraphComposefactory and shared entrypointcom.demcha.compose.document.api—DocumentSession,DocumentPageSizecom.demcha.compose.document.dsl— public DSL builderscom.demcha.compose.document.node— semantic node recordscom.demcha.compose.document.style,document.table,document.image,document.output— public value typescom.demcha.compose.document.layout— canonical functional layout pipelinecom.demcha.compose.document.backend.fixed.pdf— PDF fixed-layout backendcom.demcha.compose.document.backend.semantic— DOCX / PPTX semantic backendscom.demcha.compose.document.templates— built-in templates and datacom.demcha.compose.engine— internal ECS engine; kept for the legacy template path, not part of the recommended public APIcom.demcha.compose.font— public font registry
Please treat these names as the current source of truth in code, tests, examples, and docs. Do not introduce aliases or partial fallback imports.