diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index aeba6d4d3..b62e59355 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -11,25 +11,39 @@ labels: bug ## What actually happened - + ## How to reproduce ```java -// Minimal, runnable Java code that triggers the issue. -// Prefer the canonical `GraphCompose.document(...)` API. -DocumentSession document = GraphCompose.document(Path.of("repro.pdf")) +// Minimal, runnable Java that triggers the issue. Close the session and produce +// the output — a repro that never renders shows nothing. +try (DocumentSession document = GraphCompose.document(Path.of("repro.pdf")) .pageSize(DocumentPageSize.A4) - .create(); -// ... + .margin(24, 24, 24, 24) + .create()) { + + document.pageFlow(page -> page + .module("Repro", module -> module.paragraph("..."))); + + document.buildPdf(); // or buildPptx(Path) / toPdfBytes() +} ``` +## Generated artifact + + + ## Environment -- GraphCompose version: +- GraphCompose version: +- Output backend: +- Modules on the classpath: - Java: - OS: -- PDFBox: +- Font source: ## Additional context diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..25637456e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,14 @@ +# Routes everything that is not a bug or a feature request away from the +# issue tracker, so Issues stay a list of actionable defects and requests. +blank_issues_enabled: false + +contact_links: + - name: Usage question + url: https://github.com/DemchaAV/GraphCompose/discussions + about: How do I render X, which artifact do I need, why does my layout do this — ask in Discussions. + - name: Where to go for what + url: https://github.com/DemchaAV/GraphCompose/blob/main/SUPPORT.md + about: Channels, what to read first, and what response to expect. + - name: Security vulnerability + url: https://github.com/DemchaAV/GraphCompose/security/advisories/new + about: Report privately through a GitHub security advisory. Never in a public issue. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 199d73cb6..558ec0646 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -16,9 +16,13 @@ labels: enhancement ## Proposed API ```java -// Sketch the public-API shape you'd like to use. -// Stick to the canonical surface: GraphCompose.document(...), DocumentSession, -// document.pageFlow(...), DocumentNode + NodeDefinition, BusinessTheme. +// Sketch the public-API shape you'd like to use. Stick to the canonical surface: +// authoring GraphCompose.document(...) -> DocumentSession, document.pageFlow(...) +// new visual node DocumentNode (com.demcha.compose.document.node) +// + NodeDefinition (com.demcha.compose.document.layout) +// theming BrandTheme (com.demcha.compose.document.templates.core.theme) +// new output kind FixedLayoutBackendProvider (…document.backend.fixed) +// or SemanticBackend (…document.backend.semantic) document.pageFlow().add???(...); ``` @@ -28,6 +32,9 @@ document.pageFlow().add???(...); ## Would this be a breaking change? +Classify the surface it touches against [docs/api-stability.md](../../docs/api-stability.md) +(§1 defines the tiers, §4 maps each package onto one). + - [ ] No — additive only (new node, new builder method, new template preset). - [ ] Possibly — affects public-record shape, deprecates an existing API, or changes default rendering behaviour. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 436731e19..8b946ee85 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -25,7 +25,7 @@ Delete any heading you have nothing real to put under — never ship an empty pl - command run + result, e.g. `./mvnw -B -ntp clean verify` → BUILD SUCCESS, tests, 0 failures - the new tests and what each asserts; regenerate renders if the change is visual --> -**Lane:** +**Lane:** Closes # @@ -38,6 +38,6 @@ Closes # - [ ] `./mvnw -B -ntp clean verify` passes locally — this is the **Verification** proof above. - [ ] **Java 17 compatible** — no `getFirst()`/`getLast()`, `Thread.threadId()`, type/deconstruction `switch`, `case null, default`. (CI runs Temurin 17 / 21 / 25.) - [ ] **Public API changed** → `CHANGELOG.md` entry under the next `## v — Planned` heading. -- [ ] **README / examples touched** → `DocumentationCoverageTest` stays green; a new example is wired into `GenerateAllExamples` + the `examples/README.md` gallery count. +- [ ] **README / examples touched** → `DocumentationCoverageTest` stays green; a new example is wired into `GenerateAllExamples` and given a gallery row in `examples/README.md`. diff --git a/CHANGELOG.md b/CHANGELOG.md index c558b8433..f54c90bf8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,12 @@ follow semantic versioning; release dates are ISO 8601. - **`graph-compose-render-pptx` declares PDFBox.** It compiles against `org.apache.pdfbox` types while declaring only `fontbox`, taking the rest transitively; the resolved version is unchanged. +- **The release script owns the README release-status block.** It was a + hand-edit the script only validated, and the validation demanded the *target* + version — so between releases `develop` had to advertise an unpublished version + as "latest stable", behind a release link that 404s. `cut-release.ps1` now + promotes the in-development half to latest stable and opens the next patch line + as part of the release commit, and verifies the result after the mutation. ### Fixed @@ -69,6 +75,40 @@ follow semantic versioning; release dates are ISO 8601. snippet produces, where it fails. - **Every published module README is reachable** from the module list in the root README; eight of the ten had no inbound link from anywhere. +- **The release surface names the release that shipped.** `ROADMAP.md` described + 2.1 as in development and 2.0 as the current stable line; 2.1 is now current, + 2.0 is history, and `## Now` points at what follows. The README release-status + block, the changelog, the roadmap and the showcase metadata now agree on which + version is published and which is in development. +- **`SECURITY.md` covers every 2.x line.** The supported-versions table jumped + from `2.1.x` to `1.9.x`, so a 2.0.x user could not tell whether their version + was supported. +- **The documentation index carries the migration guide that applies.** + `docs/README.md` listed five superseded 1.x minor-to-minor upgrade guides on the + main path and omitted `migration/v2.0.0-modules.md` entirely. The current + guides are listed; the historical ones moved behind a fold. +- **The PPTX backend is documented where it lives.** `CONTRIBUTING.md`, + `SECURITY.md` and the architecture overview described the pptx module as a + semantic exporter. Its primary backend is fixed-layout + (`document.backend.fixed.pptx`), alongside the older semantic manifest; a new + fragment kind must register a handler with both fixed-layout backends or it + renders in one output and vanishes from the other. +- **Contributor instructions compile.** The "new built-in template" section + routed authors to `BusinessTheme`, `InvoiceTemplateV2` and `ProposalTemplateV2` + — none of which exist since 2.0 — and the engine-primitive section described + the entity/marker execution layer that 2.0 removed. Both now describe the + layered preset stack and the `NodeDefinition` pipeline. The bug-report template + asked for a 1.6-era version and carried a reproduction that never rendered; the + feature-request template pointed at a removed theme type; the pull-request + template offered a lane that no longer exists. +- **Usage questions route to Discussions** through a new issue-template + `config.yml`, instead of being filed as labelled issues. +- **Hand-maintained example counts are gone.** Four places stated four different + totals, none matching the catalogue. +- **The release runbook checks things that exist.** One gate grepped the README + for a test-count claim the README does not make, so it always passed; another + compared the gallery row count against the example file count, two numbers that + had long diverged; three references pointed at a migration path that moved. - **A gallery entry for a deleted example is gone.** `examples/README.md` documented rethemeing through `InvoiceTemplateV2`, a type removed in 2.0, linked a source file that no longer exists, and carried a committed preview for it. The diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8968910b9..5839fa84b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,9 +8,14 @@ Read these files first: - [README.md](./README.md) - [docs/architecture/overview.md](./docs/architecture/overview.md) -- [docs/contributing/implementation-guide.md](./docs/contributing/implementation-guide.md) +- [docs/architecture/package-map.md](./docs/architecture/package-map.md) — what lives in which package, and which of them are internal - [docs/operations/benchmarks.md](./docs/operations/benchmarks.md) when you touch benchmark tooling, render hot paths, layout hot paths, or performance-facing docs +[docs/contributing/implementation-guide.md](./docs/contributing/implementation-guide.md) +goes deeper on the engine, but parts of it still describe the execution layer that +2.0 removed — read `overview.md` and `package-map.md` first and treat the guide as +background until it is rewritten. + They explain the current public surface, the engine/template split, and the recommended extension points. ## Java 17 baseline @@ -29,8 +34,8 @@ When writing new code, avoid Java 21+ APIs and language constructs that don't ex ## Build and test - The blocking validation gate for repository work is `./mvnw -B -ntp clean verify` at the repository root — the root pom is the reactor aggregator, so this builds and verifies **every module**. For a fast inner loop while iterating on the engine, scope it to the core module: `./mvnw -B -ntp verify -pl :graph-compose-core`. -- Run the guard-focused suite with `./mvnw -B -ntp "-Dtest=EnginePdfBoundaryTest,PdfRenderInterfaceGuardTest,DocumentationCoverageTest,DocumentationExamplesTest,CanonicalSurfaceGuardTest" test`. -- Run a focused documentation sanity check with `./mvnw -B -ntp "-Dtest=DocumentationExamplesTest" test`. +- Run the engine-resident guard suite with `./mvnw -B -ntp "-Dtest=EnginePdfBoundaryTest,DocumentationCoverageTest,CanonicalSurfaceGuardTest,PackageMapGuardTest,VersionConsistencyGuardTest,CiGuardListGuardTest" test -pl :graph-compose-core` — the same list CI runs. Every name must live in `graph-compose-core`: Surefire drops a name that matches nothing as long as a sibling matches, so a guard that lives elsewhere would silently not run (`CiGuardListGuardTest` fails the build if one creeps in). +- The cross-module documentation guards — `DocumentationExamplesTest` and `DocumentationSnippetCompileTest`, which compiles the literal java fences published in `docs/` — live in `graph-compose-qa`: `./mvnw -B -ntp "-Dtest=DocumentationExamplesTest,DocumentationSnippetCompileTest" test -f qa/pom.xml`. A standalone `-f qa/pom.xml` run resolves its `graph-compose-*` dependencies from `~/.m2`, not from the reactor, so run `./mvnw -B -ntp -DskipTests install` once first — otherwise it quietly tests the artifacts you last installed instead of your working tree. - Run the local benchmark wrapper when you change performance-sensitive code or benchmark tooling: `powershell -ExecutionPolicy Bypass -File .\scripts\run-benchmarks.ps1` (Windows). To compare two branches fairly, use `scripts/ab-bench.ps1` (Windows) or the cross-platform `scripts/ab-bench.sh` (Linux/macOS/Git Bash). See [docs/operations/benchmarks.md](./docs/operations/benchmarks.md). ## How to propose changes @@ -65,12 +70,12 @@ Almost all work targets **`develop`**, the ongoing 2.x line. The `1.x` branch ta 6. **CI runs automatically.** Active jobs: - `Architecture and Documentation Guards` — fast canonical / engine-boundary guard tests, fail-first gate (always runs) - `Build and run tests (JDK 17)`, `(JDK 21)`, `(JDK 25)` — full `mvnw verify` in parallel matrix across the supported JVMs - - `Examples Generation Smoke Test` — regenerates all 54 runnable examples and uploads the PDFs as a CI artifact + - `Examples Generation Smoke Test` — regenerates every runnable example and uploads the PDFs as a CI artifact - `Binary Compatibility` — PR-only japicmp diff of the `graph-compose-core` surface - `Performance Smoke Check` — PR-only coarse benchmark to catch performance regressions - `CI Gate` — single aggregate status check that is green when every job that ran passed - **Selective on pull requests:** a `dorny/paths-filter` step skips the heavy jobs when a PR touches nothing that affects the build — a **docs-only PR runs the guards only**; `Binary Compatibility` runs only when the core module changed, and the `Performance Smoke Check` only when core / render-pdf / templates changed. Pushes to `develop` / `main` (and manual dispatch) always run the full gate. Point branch protection at **`CI Gate`** + **`Architecture and Documentation Guards`** rather than the individual matrix legs, so a docs-only PR is not left waiting on a skipped check. + **Selective on pull requests:** a `dorny/paths-filter` step skips the heavy jobs when a PR touches nothing that affects the build. Markdown counts as a build input, so a **docs-only PR still runs the reactor** — on the baseline JDK alone, and without example generation — because that is where the guards compiling the published snippets live. `Binary Compatibility` runs only when the core module changed, and the `Performance Smoke Check` only when core / render-pdf / templates changed. Pushes to `develop` / `main` (and manual dispatch) always run the full gate. Point branch protection at **`CI Gate`** + **`Architecture and Documentation Guards`** rather than the individual matrix legs, so a docs-only PR is not left waiting on a skipped check. The PR cannot merge into a protected branch until all required checks are green. 7. **Address review comments**, then squash any fixup commits before merge. The maintainer merges through GitHub once review is complete. @@ -111,8 +116,10 @@ The 2.0 GA shipped, so the branches now hold their long-term roles: Canonical functional layout pipeline: `LayoutCompiler`, `BuiltInNodeDefinitions`, `TableLayoutSupport`, `PreparedNode`, `PlacedFragment` - `render-pdf/src/main/java/com/demcha/compose/document/backend/fixed/pdf` — module **graph-compose-render-pdf** PDF backend: `PdfFixedLayoutBackend`, fragment handlers, and the option translators that bridge canonical types to PDFBox -- `render-docx/` and `render-pptx/` — modules **graph-compose-render-docx** / **graph-compose-render-pptx** - Semantic exporters `DocxSemanticBackend` (Apache POI based) and `PptxSemanticBackend` (manifest skeleton), under `com.demcha.compose.document.backend.semantic.{docx,pptx}` +- `render-pptx/src/main/java/com/demcha/compose/document/backend/fixed/pptx` — module **graph-compose-render-pptx** + PPTX fixed-layout backend: `PptxFixedLayoutBackend` and its handlers, consuming the same resolved `LayoutGraph` as the PDF backend. The module also carries the older `PptxSemanticBackend` manifest skeleton under `com.demcha.compose.document.backend.semantic.pptx` +- `render-docx/` — module **graph-compose-render-docx** + Semantic exporter `DocxSemanticBackend` (Apache POI based), under `com.demcha.compose.document.backend.semantic.docx` - `templates/src/main/java/com/demcha/compose/document/templates/*` — module **graph-compose-templates** Built-in templates (CV, cover letter, invoice, proposal, weekly schedule), DTOs, themes, registries, and scene composition helpers - `core/src/main/java/com/demcha/compose/document/showcase` @@ -202,20 +209,10 @@ not need any of them. - Keep `core/src/main/java/com/demcha/compose/engine/components/*` free of `org.apache.pdfbox` and `com.demcha.compose.engine.render.pdf` imports. -- When you add a new fragment kind, register its handler with - `PdfFixedLayoutBackend` and add or update dispatch coverage. - -Keep the entity core thin: - -- `Entity` stays 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 in `ParentContainerUpdater`. -- `Entity.bounding*` and `Entity.updateParentContainer*` are - deprecated compatibility wrappers; do not copy them into new code. -- Render-order optimizations live in the render layer (the PDF - fragment handlers), not in `Entity`. +- When you add a new fragment kind, register its handler with **both** + fixed-layout backends — `PdfFixedLayoutBackend` and `PptxFixedLayoutBackend` — + and add or update dispatch coverage. A kind registered with only one renders + in one output and silently vanishes from the other. ### Guard rails @@ -275,31 +272,25 @@ Reference templates to copy: ### New built-in template -GraphCompose supports two template authoring patterns. Pick based -on whether you're extending an existing template family or building -a new one. - -**For a NEW template family from scratch** (invoice-v2, -cover-letter-v2, report-v2, anything not yet in `cv/v2/`) — follow -the canonical layered architecture documented in +There is one template authoring pattern, whether you are adding a +new family or a new preset inside an existing one: the layered +architecture documented in [**docs/templates/v2-layered/contributor-guide.md**](./docs/templates/v2-layered/contributor-guide.md). Five sub-packages (`data/` / `theme/` / `components/` / `widgets/` -/ `presets/`), each with a clear contract. CV v2 -(`com.demcha.compose.document.templates.cv`) is the reference -implementation; read it before starting yours. - -**For a new preset inside an existing v1-classic family** (a new CV -variant alongside `ModernProfessional`, a new invoice preset -alongside `InvoiceTemplateV2`): +/ `presets/`), each with a clear contract, over the shared +`templates.core` layer. -- Constructor takes a `BusinessTheme` (or `BrandTheme` for CV - templates). Provide a no-arg overload that picks a default theme. +- Every preset is a `public final class` — no inheritance — with a + `create(BrandTheme)` factory returning `DocumentTemplate`, plus 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/templates/v1-classic/authoring.md](./docs/templates/v1-classic/authoring.md) before - starting. +- Reference implementations: + `templates.cv.presets.ModernProfessional`, + `templates.invoice.presets.ModernInvoice`, + `templates.proposal.presets.ModernProposal`. Read one before + starting yours. > 📚 **Map of template docs**: > [docs/README.md](./docs/README.md#templates) lists every template @@ -313,16 +304,17 @@ marker, a new layout system, a new render-pass session): - Decide first whether the feature belongs on the public surface as a `DocumentNode` instead. 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 the container-growth marker only to parent-like boxes that - should grow because of child content - - add `Breakable` only to entities whose own content may continue - across pages - - do not treat the container-growth marker as a pagination flag + treat the engine work as plumbing, not as new public surface. +- For genuine engine primitives, add the `NodeDefinition` that + prepares and measures the node, plus a backend-owned render handler + per fixed-layout backend. +- Pagination is declared by the definition, not by a marker: state the + split behaviour on the `NodeDefinition` itself, and compile a node + that can continue across pages through `SplittableLeafCompiler` so + the continuation indices stay monotonic. +- A container grows from the sizes its children report during + `prepare`; do not add growth as a separate signal, and do not read + it as a pagination flag. For text-heavy primitives, also read: @@ -346,7 +338,7 @@ Choose the smallest tests that match the change: - For PDF fragment-handler dispatch changes: [PdfRenderInterfaceGuardTest.java](render-pdf/src/test/java/com/demcha/compose/engine/render/pdf/PdfRenderInterfaceGuardTest.java) - For layout/positioning behavior: - [ComputedPositionTest.java](./core/src/test/java/com/demcha/compose/engine/components/layout/ComputedPositionTest.java) + [LayoutInsetsTest.java](./core/src/test/java/com/demcha/compose/document/layout/LayoutInsetsTest.java) - For pagination and multi-page behavior: [PaginationEdgeCaseTest.java](qa/src/test/java/com/demcha/compose/document/api/PaginationEdgeCaseTest.java) - For Templates v2 CV / cover-letter presets: @@ -387,7 +379,8 @@ The repository uses these normalized package roots: - `com.demcha.compose.document.style`, `document.table`, `document.image`, `document.output` — public value types - `com.demcha.compose.document.layout` — canonical functional layout pipeline - `com.demcha.compose.document.backend.fixed.pdf` — PDF fixed-layout backend -- `com.demcha.compose.document.backend.semantic` — DOCX / PPTX semantic backends +- `com.demcha.compose.document.backend.fixed.pptx` — PPTX fixed-layout backend (`@Beta`) +- `com.demcha.compose.document.backend.semantic` — semantic export SPI, the DOCX exporter, and the legacy PPTX manifest - `com.demcha.compose.document.templates` — built-in templates and data - `com.demcha.compose.engine` — internal shared engine foundation under the canonical surface; not part of the recommended public API - `com.demcha.compose.font` — public font registry diff --git a/README.md b/README.md index 17a8f0280..f80df44d0 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,8 @@

> **Release status** — -> 🟢 **Latest stable**: [v2.1.1](https://github.com/DemchaAV/GraphCompose/releases/tag/v2.1.1) — the **PowerPoint** release: `graph-compose-render-pptx` turns the same resolved layout into an editable deck — one page per slide, geometry-identical to the PDF, text and panels as native shapes. Ships as `@Beta`. **[What each backend supports ↓](docs/architecture/backend-capability-matrix.md)** +> 🟢 **Latest stable**: [v2.1.0](https://github.com/DemchaAV/GraphCompose/releases/tag/v2.1.0) — the **PowerPoint** release: `graph-compose-render-pptx` turns the same resolved layout into an editable deck — one page per slide, geometry-identical to the PDF, text and panels as native shapes. Ships as `@Beta`. **[What each backend supports ↓](docs/architecture/backend-capability-matrix.md)** +>  ·  🟡 **In development**: v2.1.1 on `develop` — see [CHANGELOG.md](./CHANGELOG.md). >  ·  ⬆️ **Upgrading from 1.x?** `graph-compose` stays a drop-in for PDF with no code change; see the [2.0 modules migration guide](./docs/migration/v2.0.0-modules.md) >  ·  See [API stability policy](./docs/api-stability.md) for tier definitions. @@ -212,71 +213,9 @@ For a Spring Boot `@RestController` streaming the PDF straight to the response, ## What's new in 2.0 -The **module-first** release — the single jar becomes a family of per-concern artifacts, so you install exactly what you render. +The **module-first** release: the single jar became a family of per-concern artifacts, so you install exactly what you render, and `graph-compose` stayed a drop-in for PDF callers. Everything the 1.9 line added ships unchanged. -- **Lean engine** — `graph-compose-core` is the document model, DSL, themes, and deterministic layout with **no PDFBox, POI, or template code** on its dependency tree. Backends plug in through a `ServiceLoader` seam; a core-only classpath asked to render throws `MissingBackendException` naming the artifact to add. -- **Opt-in render backends** — `graph-compose-render-pdf` (PDFBox 3.0, full DSL coverage), `graph-compose-render-pptx` (Apache POI, geometry-identical PowerPoint decks from the same resolved layout — one page per editable slide; clipped regions land as pixel-exact pictures; ships as **beta** in its first release), `graph-compose-render-docx` (Apache POI, semantic export). -- **`graph-compose` stays a drop-in** — the 1.x coordinate is now a thin wrapper over core + the PDF backend, so existing callers upgrade with **no code and no dependency change**. Reached the built-in templates through the single 1.x jar? Add `graph-compose-templates` (imports are unchanged) — the [migration guide](./docs/migration/v2.0.0-modules.md) walks every case, including the removed deprecated APIs and their replacements. -- **Templates are their own artifact** — the CV / cover-letter / invoice / proposal preset families moved to `graph-compose-templates` (imports unchanged). This is the [one dependency-level break](./docs/migration/v2.0.0-modules.md#the-one-break-templates) of the split. -- **`graph-compose-bundle`** — one batteries-included coordinate: PDF stack + templates + fonts + colour emoji. -- **Retired surface** — the APIs deprecated across 1.6–1.9 are removed, the layered template packages dropped their `.v2` suffix, and `BusinessTheme` plus the classic pre-layered presets are gone — each removal has a named replacement in the [migration guide](./docs/migration/v2.0.0-modules.md). - -Everything the 1.9 line added — in-document navigation, native TOC and page references, bookmarks, multi-section documents, inline chips / SVG icons / colour emoji, render-to-image — ships unchanged in 2.0. Full history in [`CHANGELOG.md`](./CHANGELOG.md). - -## Scope and comparison - -### Output support - -| Format | Status | Notes | -|---|---|---| -| PDF | Production | Fixed-layout backend on PDFBox 3.0. Full DSL coverage. | -| DOCX | Partial | Semantic export via Apache POI — paragraphs, lists, block images, tables and metadata. Word owns the flow, so drawing nodes (`shape`, `line`, `ellipse`, `barcode`) are dropped, one logged warning per kind. **Hyperlinks, bookmarks and headers/footers are not implemented**, table `colSpan`/`rowSpan` is not applied, and image fit modes are ignored — see [render-docx](./render-docx/README.md#what-it-maps-and-what-it-does-not). | -| PPTX | Beta | Fixed-layout export via Apache POI from the same resolved layout — one page per editable slide with native shapes and text frames; clipped regions land as pixel-exact pictures. First shipped in 2.1, marked `@Beta` while the API shape settles. | - -### Text & internationalization - -- Text is laid out **left-to-right**. Bidirectional (RTL) reordering and complex-script shaping — Arabic contextual joining, Indic reordering — are **not** performed, so Arabic / Hebrew text renders in logical order without correct visual ordering. Full RTL / bidi support is tracked in [#140](https://github.com/DemchaAV/GraphCompose/issues/140). -- A glyph the active font does not cover renders as `?` (with a warning logged); load a font that covers the script you need. - -### When to use GraphCompose - -- **Server-side PDF generation in Java** — invoices, CVs, reports, proposals, statements, schedules. -- **Templated documents from data** — themed presets (`ModernProfessional`, `ModernInvoice`, …) you parameterise instead of re-styling every time. -- **Regression-tested layouts** — `DocumentSession#layoutSnapshot()` makes layout changes visible in PRs before any byte ships; `PdfVisualRegression` adds a pixel-level gate for font and colour fidelity. -- **Streaming PDFs from web backends** — Spring Boot `@RestController` writing straight to the response ([`HttpStreamingExample`](./examples/src/main/java/com/demcha/examples/features/streaming/HttpStreamingExample.java)). -- **Higher-level than PDFBox, lighter than JasperReports** — Java DSL describes semantics; no XML templates, no manual coordinates. - -### What GraphCompose is not - -- Not a hosted PDF rendering service — it is a library you embed. -- Not a WYSIWYG editor — the DSL is code, not drag-and-drop. -- Not a reporting engine like JasperReports — no datasource bindings, no XML templates, no compiled `.jasper` files. -- Not a browser / HTML-to-PDF renderer — the engine has its own layout pipeline; HTML/CSS input is not supported. - -### Compared with similar Java libraries - -| Library | API style | Layout | License | Best for | -|---|---|---|---|---| -| **GraphCompose** | Java DSL, semantic nodes | Two-pass, deterministic, snapshot-testable | MIT | Code-first business documents with layout regression tests | -| **PDFBox** | Low-level text / path primitives | Manual coordinates | Apache 2.0 | Direct PDF manipulation, parsing, extraction | -| **iText 7** | Object/layout API + low-level canvas | Automatic layout with direct-positioning options | AGPL / commercial | When AGPL is acceptable or you have a commercial licence | -| **OpenPDF** | iText 4 fork | Manual + helpers | LGPL / MPL | Legacy iText 4 codebases | -| **JasperReports** | XML templates compiled to `.jasper` | Template-driven | LGPL | Tabular reports with datasource bindings | - -GraphCompose uses PDFBox under the hood as the rendering backend — the comparison is about authoring surface, not the renderer. - -### Which API should I use? - -| You want to… | Surface | Entry point | -|---|---|---| -| Generate a one-off PDF programmatically | DSL | `GraphCompose.document(...).pageFlow(...)` — see [Hello world](#hello-world) above | -| Generate a CV / cover letter from data | Layered templates | `ModernProfessional.create().compose(session, cvDocument)` — see [layered templates](./docs/templates/v2-layered/README.md) | -| Add a custom visual primitive | Engine extension | `NodeDefinition` + `PdfFragmentRenderHandler` — see [extension guide](./docs/contributing/extension-guide.md) | -| Regression-test generated layouts | Layout snapshots | `DocumentSession#layoutSnapshot()` — quickstart at [Testing your document](./docs/operations/test-your-document.md); full reference at [snapshot testing](./docs/operations/layout-snapshot-testing.md) | -| Pixel-test the rendered PDF (fonts, colours, anti-aliasing) | Visual regression | `PdfVisualRegression.standard()…assertMatchesBaseline(...)` — see [visual regression testing](./docs/operations/visual-regression-testing.md) | -| See the live gallery | Static showcase site | [Showcase](https://DemchaAV.github.io/GraphCompose/) — source under [`web/`](./web), deployed to GitHub Pages via the [Pages workflow](./.github/workflows/deploy-web.yml) | - -> **Templates in 2.0** — there is one template surface: the layered preset families in `graph-compose-templates`, themed through `BrandTheme`. Arriving from a pre-2.0 surface (classic presets, the built-in `*Template` classes)? **[Which template system should I use?](./docs/templates/which-template-system.md)** maps every retired name to its layered replacement. +Full detail in [`CHANGELOG.md`](./CHANGELOG.md); every removed API and its replacement in the [2.0 modules migration guide](./docs/migration/v2.0.0-modules.md). ## Vector primitives in 30 lines @@ -345,24 +284,73 @@ The repository is a Maven multi-module reactor: the root `pom.xml` is the build See [CONTRIBUTING](./CONTRIBUTING.md) for the branch-routing table and the full build / verify flow. -## Documentation +## Scope and comparison -📚 **[Full docs index](./docs/README.md)** — categorised map of every doc, ADR, and recipe. Start there to navigate the documentation. +### Output support + +| Format | Status | Notes | +|---|---|---| +| PDF | Production | Fixed-layout backend on PDFBox 3.0. Full DSL coverage. | +| DOCX | Partial | Semantic export via Apache POI — paragraphs, lists, block images, tables and metadata. Word owns the flow, so drawing nodes (`shape`, `line`, `ellipse`, `barcode`) are dropped, one logged warning per kind. **Hyperlinks, bookmarks and headers/footers are not implemented**, table `colSpan`/`rowSpan` is not applied, and image fit modes are ignored — see [render-docx](./render-docx/README.md#what-it-maps-and-what-it-does-not). | +| PPTX | Beta | Fixed-layout export via Apache POI from the same resolved layout — one page per editable slide with native shapes and text frames; clipped regions land as pixel-exact pictures. First shipped in 2.1, marked `@Beta` while the API shape settles. | + +### Text & internationalization + +- Text is laid out **left-to-right**. Bidirectional (RTL) reordering and complex-script shaping — Arabic contextual joining, Indic reordering — are **not** performed, so Arabic / Hebrew text renders in logical order without correct visual ordering. Full RTL / bidi support is tracked in [#140](https://github.com/DemchaAV/GraphCompose/issues/140). +- A glyph the active font does not cover renders as `?` (with a warning logged); load a font that covers the script you need. -### Templates -- [**Templates — layered architecture**](./docs/templates/v2-layered/README.md) — the template surface: CV, cover-letter, invoice, and proposal preset families on `BrandTheme`. Personas: [quickstart](./docs/templates/v2-layered/quickstart.md) · [using templates](./docs/templates/v2-layered/using-templates.md) · [authoring presets](./docs/templates/v2-layered/authoring-presets.md) · [contributing a new family](./docs/templates/v2-layered/contributor-guide.md). -- [Which template system?](./docs/templates/which-template-system.md) — the template naming history and the migration map for callers arriving from a pre-2.0 surface (classic presets, built-in `*Template` classes, the legacy PDF API). The retired classic docs are archived at [v1-classic](./docs/templates/v1-classic/README.md). +### When to use GraphCompose + +- **Server-side PDF generation in Java** — invoices, CVs, reports, proposals, statements, schedules. +- **Templated documents from data** — themed presets (`ModernProfessional`, `ModernInvoice`, …) you parameterise instead of re-styling every time. +- **Regression-tested layouts** — `DocumentSession#layoutSnapshot()` makes layout changes visible in PRs before any byte ships; `PdfVisualRegression` adds a pixel-level gate for font and colour fidelity. +- **Streaming PDFs from web backends** — Spring Boot `@RestController` writing straight to the response ([`HttpStreamingExample`](./examples/src/main/java/com/demcha/examples/features/streaming/HttpStreamingExample.java)). +- **Higher-level than PDFBox, lighter than JasperReports** — Java DSL describes semantics; no XML templates, no manual coordinates. + +### What GraphCompose is not + +- Not a hosted PDF rendering service — it is a library you embed. +- Not a WYSIWYG editor — the DSL is code, not drag-and-drop. +- Not a reporting engine like JasperReports — no datasource bindings, no XML templates, no compiled `.jasper` files. +- Not a browser / HTML-to-PDF renderer — the engine has its own layout pipeline; HTML/CSS input is not supported. -### Architecture & operations -- [Architecture overview](./docs/architecture/overview.md) · [Lifecycle](./docs/architecture/lifecycle.md) · [Production rendering](./docs/operations/production-rendering.md) · [Benchmarks](./docs/operations/benchmarks.md) · [Layout snapshot testing](./docs/operations/layout-snapshot-testing.md) · [Troubleshooting](./docs/troubleshooting.md) +### Compared with similar Java libraries + +| Library | API style | Layout | License | Best for | +|---|---|---|---|---| +| **GraphCompose** | Java DSL, semantic nodes | Two-pass, deterministic, snapshot-testable | MIT | Code-first business documents with layout regression tests | +| **PDFBox** | Low-level text / path primitives | Manual coordinates | Apache 2.0 | Direct PDF manipulation, parsing, extraction | +| **iText 7** | Object/layout API + low-level canvas | Automatic layout with direct-positioning options | AGPL / commercial | When AGPL is acceptable or you have a commercial licence | +| **OpenPDF** | iText 4 fork | Manual + helpers | LGPL / MPL | Legacy iText 4 codebases | +| **JasperReports** | XML templates compiled to `.jasper` | Template-driven | LGPL | Tabular reports with datasource bindings | + +GraphCompose uses PDFBox under the hood as the rendering backend — the comparison is about authoring surface, not the renderer. + +### Which API should I use? + +| You want to… | Surface | Entry point | +|---|---|---| +| Generate a one-off PDF programmatically | DSL | `GraphCompose.document(...).pageFlow(...)` — see [Hello world](#hello-world) above | +| Generate a CV / cover letter from data | Layered templates | `ModernProfessional.create().compose(session, cvDocument)` — see [layered templates](./docs/templates/v2-layered/README.md) | +| Add a custom visual primitive | Engine extension | `NodeDefinition` + a fragment handler per fixed-layout backend (`PdfFragmentRenderHandler`, `PptxFragmentRenderHandler`) — see [extension guide](./docs/contributing/extension-guide.md) | +| Regression-test generated layouts | Layout snapshots | `DocumentSession#layoutSnapshot()` — quickstart at [Testing your document](./docs/operations/test-your-document.md); full reference at [snapshot testing](./docs/operations/layout-snapshot-testing.md) | +| Pixel-test the rendered PDF (fonts, colours, anti-aliasing) | Visual regression | `PdfVisualRegression.standard()…assertMatchesBaseline(...)` — see [visual regression testing](./docs/operations/visual-regression-testing.md) | +| See the live gallery | Static showcase site | [Showcase](https://DemchaAV.github.io/GraphCompose/) — source under [`web/`](./web), deployed to GitHub Pages via the [Pages workflow](./.github/workflows/deploy-web.yml) | + +> **Templates in 2.0** — there is one template surface: the layered preset families in `graph-compose-templates`, themed through `BrandTheme`. Arriving from a pre-2.0 surface (classic presets, the built-in `*Template` classes)? **[Which template system should I use?](./docs/templates/which-template-system.md)** maps every retired name to its layered replacement. + +## Documentation + +📚 **[Full docs index](./docs/README.md)** — categorised map of every doc, ADR, and recipe. Start there to navigate the documentation. -### Recipes & examples -- [Recipes index](./docs/recipes.md) — [shape-as-container](./docs/recipes/shape-as-container.md) · [shapes](./docs/recipes/shapes.md) · [transforms](./docs/recipes/transforms.md) · [page-backgrounds](./docs/recipes/page-backgrounds.md) · [layered-page-design](./docs/recipes/layered-page-design.md) · [absolute-placement](./docs/recipes/absolute-placement.md) · [tables](./docs/recipes/tables.md) · [themes](./docs/recipes/themes.md) · [streaming](./docs/recipes/streaming.md) · [extending](./docs/recipes/extending.md) · [font-coverage](./docs/font-coverage.md) -- [Examples gallery](./examples/README.md) — every runnable example with PDF preview +The index routes by what you are doing — first document, using or authoring a +template, extending the engine, running in production. The entry points most +people want directly: -### Contributing & releases -- [Contributing](./CONTRIBUTING.md) · [Code of conduct](./CODE_OF_CONDUCT.md) · [Security policy](./SECURITY.md) · [Release process](./docs/contributing/release-process.md) -- [API stability policy](./docs/api-stability.md) · [Which template system?](./docs/templates/which-template-system.md) · [**Migration to 2.0 (modules)**](./docs/migration/v2.0.0-modules.md) · [older migration notes](./docs/README.md) +- **Templates** — [layered architecture](./docs/templates/v2-layered/README.md) (CV, cover letter, invoice, proposal on `BrandTheme`) · [which template system?](./docs/templates/which-template-system.md) for callers arriving from a pre-2.0 surface +- **Recipes** — [the cookbook](./docs/recipes.md): tables, themes, shapes, transforms, page backgrounds, streaming, extending +- **Operations** — [production rendering](./docs/operations/production-rendering.md) · [layout snapshot testing](./docs/operations/layout-snapshot-testing.md) · [troubleshooting](./docs/troubleshooting.md) +- **Project** — [Contributing](./CONTRIBUTING.md) · [Roadmap](./ROADMAP.md) · [Support](./SUPPORT.md) · [Security policy](./SECURITY.md) · [API stability](./docs/api-stability.md) · [Migration to 2.0](./docs/migration/v2.0.0-modules.md) ## Companion projects diff --git a/ROADMAP.md b/ROADMAP.md index b596bbe32..98fa0d4d3 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,18 +1,22 @@ # Roadmap -GraphCompose is solo-maintained. This roadmap is a direction, not a contract. Dates are intentionally omitted. Concrete work is tracked in [issues](https://github.com/DemchaAV/GraphCompose/issues) and shipped work is recorded in [CHANGELOG.md](CHANGELOG.md). For v1.6 phase-level detail, see [docs/roadmaps/v1.6-roadmap.md](docs/roadmaps/v1.6-roadmap.md). +GraphCompose is solo-maintained. This roadmap is a direction, not a contract. Dates are intentionally omitted. Concrete work is tracked in [issues](https://github.com/DemchaAV/GraphCompose/issues) and shipped work is recorded in [CHANGELOG.md](CHANGELOG.md). -## Now — 2.1 line +## Now — after 2.1 -In development on `develop`, building toward the **2.1.0** minor. The headline is the **fixed-layout PPTX render backend**: the same `DocumentSession` that prints a PDF now also emits an editable PowerPoint deck — one page per slide, identical geometry by construction, native shapes. It ships as `@Beta` (Experimental) while its API shape settles; the geometry identity with the PDF backend is a design invariant, not subject to change. See the [API stability policy](docs/api-stability.md) and the [backend capability matrix](docs/architecture/backend-capability-matrix.md). +Consolidation ahead of the next patch: the documentation and contributor surfaces are being brought onto the 2.x vocabulary, the CI guards onto what they actually run, and the committed example assets onto a regeneration path. Open engineering threads are tracked in [issues](https://github.com/DemchaAV/GraphCompose/issues) — vector clipping for the PPTX backend, backend-neutral font measurement, and font-face selection are the live ones. -Alongside it, 2.1 hardens pagination: `keepWithNext()` for headings, orphaned-heading fixes ahead of paragraphs, tables and lists, and the layered CV presets wired to the keep-with-next policy. +## Current stable — 2.1 -Full detail lands in [CHANGELOG.md](CHANGELOG.md) under `v2.1.0`. +The **2.1.0** GA is the current stable line. Its headline is the **fixed-layout PPTX render backend**: the same `DocumentSession` that prints a PDF also emits an editable PowerPoint deck — one page per slide, identical geometry by construction, native shapes. It ships as `@Beta` (Experimental) while its API shape settles; the geometry identity with the PDF backend is a design invariant, not subject to change. See the [API stability policy](docs/api-stability.md) and the [backend capability matrix](docs/architecture/backend-capability-matrix.md). -## Current stable — 2.0 +2.1 also hardened pagination: `keepWithNext()` for headings, orphaned-heading fixes ahead of paragraphs, tables and lists, and the layered CV presets wired to the keep-with-next policy. -The **2.0.0** GA is the current stable line. 2.0 was about **packaging and internal hygiene**, not new authoring API — binary-breaking by design. +Full detail in [CHANGELOG.md](CHANGELOG.md) under `v2.1.0`. + +## Previously — 2.0 + +The **2.0.0** GA opened the 2.x line. 2.0 was about **packaging and internal hygiene**, not new authoring API — binary-breaking by design. - **Modular split** — the single jar is split into `graph-compose-core` plus `graph-compose-render-pdf` / `graph-compose-render-docx` / `graph-compose-render-pptx` / `graph-compose-templates` / `graph-compose-testing`, with render backends discovered through a `ServiceLoader` SPI. `graph-compose` stays a drop-in wrapper (core + render-pdf) so existing PDF callers upgrade unchanged. See the [2.0 modules migration guide](docs/migration/v2.0.0-modules.md) and [ADR 0016](docs/adr/0016-multi-module-packaging.md). - **Legacy removal** — the dead Entity-Component-System execution layer and the deprecated (`forRemoval`) public API are gone; the classic template presets are replaced by the layered `templates.*` stack on `BrandTheme`. diff --git a/SECURITY.md b/SECURITY.md index b195b1680..8d35966cc 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -9,7 +9,9 @@ Security fixes are issued for the latest minor release. Older minors do not rece | Version | Supported | |---------|-----------| | 2.1.x | Yes — actively patched | -| < 2.1 | No — upgrade required (see [CHANGELOG.md](CHANGELOG.md) for per-version migration notes, and the [2.0 modules migration guide](docs/migration/v2.0.0-modules.md) for the 1.x → 2.x step) | +| 2.0.x | No — upgrade to the latest 2.1.x; it is source-compatible, see [CHANGELOG.md](CHANGELOG.md) | +| 1.9.x | Critical fixes and security backports only, on the `1.x` branch — no features | +| < 1.9 | No — upgrade required (see [CHANGELOG.md](CHANGELOG.md) for per-version migration notes, and the [2.0 modules migration guide](docs/migration/v2.0.0-modules.md) for the 1.x → 2.x step) | ## Reporting a vulnerability @@ -44,7 +46,8 @@ In scope: - Layout engine (`com.demcha.compose.document.layout`). - PDF backend (`com.demcha.compose.document.backend.fixed.pdf`). -- DOCX / PPTX semantic backends (`com.demcha.compose.document.backend.semantic`). +- PPTX backend (`com.demcha.compose.document.backend.fixed.pptx`). +- Semantic export backends (`com.demcha.compose.document.backend.semantic`) — DOCX, and the legacy PPTX manifest. - Templates shipped in `com.demcha.compose.document.templates`. - Public authoring API (`GraphCompose`, `DocumentSession`, DSL). - Build and release artifacts on Maven Central (`io.github.demchaav:graph-compose`). The legacy JitPack URL remains available for consumers pinned to v1.6.5 and earlier but is no longer the documented install channel. diff --git a/SUPPORT.md b/SUPPORT.md index 46512f4ef..2ac0e6757 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -6,7 +6,7 @@ GraphCompose is a solo-maintained open-source library. This file describes where | Need | Where | |------|-------| -| Usage question | [GitHub Issues](https://github.com/DemchaAV/GraphCompose/issues/new?labels=question&title=Question%3A+) — apply the `question` label | +| Usage question | [GitHub Discussions](https://github.com/DemchaAV/GraphCompose/discussions) — not the issue tracker | | Bug report | [Bug report template](https://github.com/DemchaAV/GraphCompose/issues/new?template=bug_report.md) | | Feature request | [Feature request template](https://github.com/DemchaAV/GraphCompose/issues/new?template=feature_request.md) | | Security vulnerability | [SECURITY.md](SECURITY.md) — do **not** use public issues | @@ -16,12 +16,14 @@ GraphCompose is a solo-maintained open-source library. This file describes where Self-serve, ordered by likelihood of containing the answer: -1. [README.md](README.md) — installation, hello-world, v1.6 primitives. -2. [docs/getting-started.md](docs/getting-started.md) — first runnable example. -3. [examples/README.md](examples/README.md) — runnable examples with PDF previews. -4. [docs/recipes.md](docs/recipes.md) — patterns for common layouts. -5. [docs/architecture/overview.md](docs/architecture/overview.md) and [docs/architecture/package-map.md](docs/architecture/package-map.md) — when extending the engine. -6. [docs/roadmaps/migration-v1-5-to-v1-6.md](docs/roadmaps/migration-v1-5-to-v1-6.md) — upgrades from previous minor. +1. [README.md](README.md) — installation, hello-world, which artifact to depend on. +2. [docs/first-document.md](docs/first-document.md) — the five-minute path to a rendered PDF. +3. [docs/getting-started.md](docs/getting-started.md) — DSL or templates, and the first-render walk-through. +4. [examples/README.md](examples/README.md) — runnable examples with PDF previews. +5. [docs/recipes.md](docs/recipes.md) — patterns for common layouts. +6. [docs/troubleshooting.md](docs/troubleshooting.md) — symptom-first fixes for the common gotchas. +7. [docs/architecture/overview.md](docs/architecture/overview.md) and [docs/architecture/package-map.md](docs/architecture/package-map.md) — when extending the engine. +8. [docs/migration/v2.0.0-modules.md](docs/migration/v2.0.0-modules.md) — upgrading from the 1.x line; [CHANGELOG.md](CHANGELOG.md) for minor-to-minor steps. ## Response expectations diff --git a/docs/README.md b/docs/README.md index 3bce26e42..03acd26a8 100644 --- a/docs/README.md +++ b/docs/README.md @@ -62,14 +62,21 @@ back here. - **[contributing/implementation-guide.md](contributing/implementation-guide.md)** — internal engine notes for contributors hacking on layout / measurement / pagination. - **[contributing/release-process.md](contributing/release-process.md)** — versioning, tag procedure, Maven Central publication. -### Roadmaps & migrations -- **[roadmaps/post-2.0-engineering.md](roadmaps/post-2.0-engineering.md)** — forward-looking engineering roadmap: internal refactors, scale, and tooling deferred past the 2.0 line. -- **[roadmaps/v1.6-roadmap.md](roadmaps/v1.6-roadmap.md)** — the v1.6 "expressive" roadmap (shipped). -- **[roadmaps/migration-v1-8-to-v1-9.md](roadmaps/migration-v1-8-to-v1-9.md)** — upgrade guide for v1.8 → v1.9. -- **[roadmaps/migration-v1-7-to-v1-8.md](roadmaps/migration-v1-7-to-v1-8.md)** — upgrade guide for v1.7 → v1.8. -- **[roadmaps/migration-v1-6-to-v1-7.md](roadmaps/migration-v1-6-to-v1-7.md)** — upgrade guide for v1.6 → v1.7. -- **[roadmaps/migration-v1-5-to-v1-6.md](roadmaps/migration-v1-5-to-v1-6.md)** — upgrade guide for v1.5 → v1.6. -- **[roadmaps/migration-v1-4-to-v1-5.md](roadmaps/migration-v1-4-to-v1-5.md)** — upgrade guide for v1.4 → v1.5. +### Migrations & roadmap +- **[migration/v2.0.0-modules.md](migration/v2.0.0-modules.md)** — the current upgrade guide: the 1.x → 2.x module split, the one dependency-level break, and every removed API with its replacement. +- **[migration/v1.8.0-fonts.md](migration/v1.8.0-fonts.md)** — the fonts artifact split, still relevant to anyone adding `graph-compose-fonts`. +- **[../ROADMAP.md](../ROADMAP.md)** — direction for the line after 2.1. +- **[roadmaps/post-2.0-engineering.md](roadmaps/post-2.0-engineering.md)** — internal refactors, scale, and tooling deferred past the 2.0 line. + +
+Historical documentation — shipped roadmaps and superseded minor-to-minor upgrade guides + +Kept for anyone stepping through the 1.x line one minor at a time. Nothing here describes the current API. + +- [roadmaps/v1.6-roadmap.md](roadmaps/v1.6-roadmap.md) — the v1.6 "expressive" roadmap (shipped). +- [roadmaps/migration-v1-8-to-v1-9.md](roadmaps/migration-v1-8-to-v1-9.md) · [v1-7-to-v1-8](roadmaps/migration-v1-7-to-v1-8.md) · [v1-6-to-v1-7](roadmaps/migration-v1-6-to-v1-7.md) · [v1-5-to-v1-6](roadmaps/migration-v1-5-to-v1-6.md) · [v1-4-to-v1-5](roadmaps/migration-v1-4-to-v1-5.md) + +
### Recipes (cookbook-style howtos) - **[recipes.md](recipes.md)** — index of all recipes (coverage map in [recipes/README.md](recipes/README.md)). diff --git a/docs/architecture/canonical-legacy-parity.md b/docs/architecture/canonical-legacy-parity.md index 297be5243..64dcc47c3 100644 --- a/docs/architecture/canonical-legacy-parity.md +++ b/docs/architecture/canonical-legacy-parity.md @@ -1,8 +1,10 @@ # Canonical Legacy-Parity Matrix -This matrix tracks practical authoring parity for the canonical session-first API. -It is a living planning document (last refreshed for v1.5), not a request to -expose the old low-level authoring model. +This matrix tracks practical authoring coverage of the canonical session-first +API, feature by feature. It is a living document, last refreshed for the 2.1 +line. The low-level authoring model it was originally measured against was +removed in 2.0; the matrix is kept because the per-feature coverage it records +is what the recipes, capabilities catalogue and troubleshooting guide link to. Public application code should start with: @@ -33,7 +35,7 @@ reaching into engine internals. | Child horizontal alignment | Done via `LayerStack` / `ShapeContainer` | Use `LayerStackBuilder.topLeft(...)` … `bottomRight(...)` (nine alignment shortcuts) or `ShapeContainerBuilder` with the same vocabulary; v1.5 also exposes `position(node, offsetX, offsetY, anchor)` for screen-space nudges from an anchor. | | Child vertical alignment | Done via `LayerStack` / `ShapeContainer` | Same nine alignment anchors cover top, centre, bottom edges. | | Absolute placement | Rejected for normal authoring | Keep coordinates inside layout and backend internals. Use `LayerStack.position(...)` if you need anchor-plus-offset placement. | -| Controlled free canvas (`CanvasLayerNode`) | Planned for v1.6 (stretch) | New atomic semantic node accepting children at explicit `(x, y)` coordinates. Separate from `LayerStackNode` and `ShapeContainerNode` so absolute placement stays an opt-in primitive, not a global policy. See [v1.6 roadmap, Phase C](../roadmaps/v1.6-roadmap.md). | +| Controlled free canvas (`CanvasLayerNode`) | Done | New atomic semantic node accepting children at explicit `(x, y)` coordinates. Separate from `LayerStackNode` and `ShapeContainerNode` so absolute placement stays an opt-in primitive, not a global policy. | ## Visual Primitives @@ -56,7 +58,7 @@ reaching into engine internals. | Paragraph wrapping | Done | Keep wrapping in paragraph layout preparation. | | Inline text/link runs | Done | Keep as paragraph-level semantic content. | | Lists | Done | Existing marker and indentation APIs remain canonical. | -| Nested list ergonomics | Planned for v1.6 (committed) | `ListBuilder.addItem(label, Consumer)` plus a new `ListItem` value type and a `ListNode` record extension with back-compat constructor. See [v1.6 roadmap, Phase A](../roadmaps/v1.6-roadmap.md). | +| Nested list ergonomics | Done | `ListBuilder.addItem(label, Consumer)` plus a new `ListItem` value type and a `ListNode` record extension with back-compat constructor. | | Auto-size text | Done | Use `ParagraphBuilder.autoSize(maxSize, minSize)` to fit single-line headlines into the resolved inner width. | ## Images @@ -80,7 +82,7 @@ reaching into engine internals. | Zebra rows | Done | `TableBuilder.zebra(odd, even)` (style or colour overload) alternates row fills. Applied lazily at `build()`; explicit `rowStyle(idx, ...)` always wins. | | Totals row | Done | `TableBuilder.totalRow(values)` appends a row with a default bold + subtle-fill style. `totalRow(style, values)` for a custom look. | | Repeated header on page break | Done | `TableBuilder.repeatHeader()` repeats the first row at the top of every continuation page; `repeatHeader(int n)` for multi-row headers. | -| Complex cell composition | Planned for v1.6 (committed) | New `TableCellContent.NodeContent(DocumentNode child)` variant + `DocumentTableCell.node(DocumentNode)` factory. Two-pass cell measurement preserves row pagination behaviour. See [v1.6 roadmap, Phase B](../roadmaps/v1.6-roadmap.md). | +| Complex cell composition | Done | New `TableCellContent.NodeContent(DocumentNode child)` variant + `DocumentTableCell.node(DocumentNode)` factory. Two-pass cell measurement preserves row pagination behaviour. | ## PDF Output diff --git a/docs/architecture/overview.md b/docs/architecture/overview.md index 67a8b5e9e..699b16fe9 100644 --- a/docs/architecture/overview.md +++ b/docs/architecture/overview.md @@ -245,7 +245,9 @@ last: - `com.demcha.compose.document.*` — **public canonical surface**. Authoring API, layout graph, exceptions, snapshots. The render backends (`document.backend.fixed.pdf` in **graph-compose-render-pdf**, - `document.backend.semantic.*` in the docx / pptx modules) and the built-in + `document.backend.fixed.pptx` plus the legacy `document.backend.semantic.pptx` + in **graph-compose-render-pptx**, `document.backend.semantic.docx` in + **graph-compose-render-docx**) and the built-in templates (`document.templates.*` in **graph-compose-templates**) share this namespace but ship as separate, opt-in artifacts over `graph-compose-core`. - `com.demcha.compose.font.*` — public font names, backend-neutral diff --git a/docs/contributing/release-process.md b/docs/contributing/release-process.md index cd2ca5be0..49276f2ad 100644 --- a/docs/contributing/release-process.md +++ b/docs/contributing/release-process.md @@ -32,20 +32,21 @@ The shell setup and exact PowerShell commands live in the `graphcompose-release- - [ ] `./mvnw -B -ntp clean verify` exits 0 over the whole reactor. Every test must pass — no skips, no flake retries. Confirm `Tests run: , Failures: 0, Errors: 0, Skipped: 0` per module from `*/target/surefire-reports/*.txt`. **Read Maven's own exit code** — never end the command in a pipe, or the shell reports the last stage's status and a `BUILD FAILURE` slips through as `0`. - [ ] Examples module compiles cleanly: `./mvnw -B -ntp -q -f examples/pom.xml clean compile` exits 0. Catches `double → float` lossy narrowing and similar bugs that don't surface in the engine module. -- [ ] All examples regenerate: `./mvnw -B -ntp -q -f examples/pom.xml exec:java -Dexec.mainClass=com.demcha.examples.GenerateAllExamples` produces 26+ `Generated:` lines, exits 0, and emits no `Fixed column ... is smaller than required natural width` or `Spanned cell ... requires extra width` errors. (Requires `./mvnw install -DskipTests` once first so the local `~/.m2` resolves the current SNAPSHOT version — any standalone goal that resolves train modules from `~/.m2`, including the `qa` suite and `javadoc:javadoc`, needs this after a version bump.) -- [ ] Architecture-guard suite explicitly green: `./mvnw -B -ntp test -pl :graph-compose-core -Dtest='CanonicalSurfaceGuardTest,DocumentationCoverageTest,InternalAnnotationCoverageTest,PublicApiNoEngineLeakTest,PackageMapGuardTest,VersionConsistencyGuardTest'` exits 0. These guard against legacy-API leakage in docs and engine internals leaking into the public surface, and — via `VersionConsistencyGuardTest` — against version drift between the train poms and the README install snippets. Note `PackageMapGuardTest` also scans the gitignored `docs/private/`, so a stale local note there fails a local run that CI cannot reproduce. +- [ ] All examples regenerate: `./mvnw -B -ntp -q -f examples/pom.xml exec:java -Dexec.mainClass=com.demcha.examples.GenerateAllExamples` exits 0, prints one `Generated:` line per example, and emits no `Fixed column ... is smaller than required natural width` or `Spanned cell ... requires extra width` errors. (Requires `./mvnw install -DskipTests` once first so the local `~/.m2` resolves the current SNAPSHOT version — any standalone goal that resolves train modules from `~/.m2`, including the `qa` suite and `javadoc:javadoc`, needs this after a version bump.) +- [ ] Architecture-guard suite explicitly green: `./mvnw -B -ntp test -pl :graph-compose-core -Dtest='CanonicalSurfaceGuardTest,DocumentationCoverageTest,InternalAnnotationCoverageTest,PublicApiNoEngineLeakTest,PackageMapGuardTest,VersionConsistencyGuardTest,CiGuardListGuardTest'` exits 0. These guard against legacy-API leakage in docs and engine internals leaking into the public surface, and — via `VersionConsistencyGuardTest` — against version drift between the train poms and the README install snippets. Both markdown guards skip the gitignored `docs/private/`, so local planning notes cannot fail a run that CI is unable to reproduce. - [ ] Javadoc gate green on the published modules: `./mvnw -B -ntp javadoc:javadoc -pl :graph-compose-core,:graph-compose-render-pdf,:graph-compose-render-docx,:graph-compose-render-pptx,:graph-compose-templates,:graph-compose-testing` exits 0. The release profile publishes a javadoc jar per module with `failOnError=false`, so a broken link only surfaces here. ### C. Documentation freeze (matches target version) - [ ] `CHANGELOG.md` has a `## v — Planned` header at the top. The script flips `Planned` → today's date during release execution; if the header is missing or already dated, the script silently skips and the release ships with the wrong header. -- [ ] CHANGELOG `v` section: every linked file resolves on disk. Common offenders: new `docs/adr/00XX-*.md`, `docs/migration-v1-N-to-v1-M.md`, recipe pages. -- [ ] `README.md` test-count claim matches the actual surefire total (`grep -E '[0-9]+ green tests' README.md` vs the surefire aggregate). +- [ ] CHANGELOG `v` section: every linked file resolves on disk. Common offenders: new `docs/adr/00XX-*.md`, `docs/migration/*.md`, recipe pages. +- [ ] `README.md` carries no hand-maintained count (test totals, example totals) that the release would falsify. Numbers in prose have no owner and no guard — prefer prose that stays true. - [ ] `README.md` install snippets match the **current** `pom.xml` version (on `develop` between releases that is the last published version). `VersionConsistencyGuardTest` enforces README == pom, so the two move together: `cut-release.ps1` rewrites the README Maven + Gradle install snippets to the new version in the *same* release commit it bumps the POMs (section 1, Step 2/6). The README therefore flips to the new version at release-execution time, never on `develop` ahead of the tag — a snippet pointing at a version that has not been published yet would 404 for any user who copies it. Do **not** hand-flip the README ahead of the script: that desyncs README from the still-unbumped pom and fails the guard at the verify gate. -- [ ] **`README.md` "Release status" prose block** (the `> 🟢 Latest stable: vX … 🟡 In develop: vY` blockquote near the top) names the **NEW target** as "Latest stable" and the **next cycle** as "In develop", with the new release's one-line highlights. ⚠️ `cut-release.ps1` does **not** touch this block (it only rewrites the install snippets), and the post-release cycle-open commit that would fix it is **develop-only** and never reaches `main`. So it MUST be updated **on develop before the cut** (in the `docs: pre-release fixes` commit, section 2.A) — otherwise `main` (the README GitHub renders) keeps advertising the *previous* version as "latest stable". Not guarded by a test; this checklist item is the only gate. +- [ ] `ROADMAP.md` names the version being released as the current stable line, and `## Now` points at what comes after it. Not guarded by a test. +- [ ] **`README.md` "Release status" prose block** (the `> 🟢 Latest stable: vX … 🟡 In development: vY` blockquote near the top) is **script-owned since v2.1.1**: `cut-release.ps1` Step 1 promotes the in-development half to latest stable and opens the next patch line, and `Assert-ReleaseMetadata` verifies it after the mutation. What you own is the **prose** — the one-line highlight after the version. Update it on develop before the cut so the release commit carries the right description to `main`; the version tokens take care of themselves. Step 0 only checks the block still exists in the shape the rewrite matches, so a reflowed blockquote aborts loudly instead of being skipped in silence. - [ ] `README.md` and `examples/README.md` link audits resolve: every `(./...)` and `(../...)` link must exist on disk. Use `grep -oE '\(\.?\.?/[^)]+\.(md|java|png|pdf|jpg)\)' README.md examples/README.md | sed 's/^(//;s/)$//' | sort -u | xargs -I{} test -e {} || echo MISSING: {}`. -- [ ] `examples/README.md` gallery row count matches the file count: `find examples/src/main/java -name '*Example.java' | wc -l` equals `grep -c '^| \[' examples/README.md`. -- [ ] For minor releases (`vX.Y.0`): `docs/migration-v1--to-v1-.md` exists. Patch releases skip this. +- [ ] Every example the runner produces has a `ShowcaseMetadata` entry, so the published site carries no placeholder cards: compare `examples/target/generated-pdfs` against the entries in `examples/src/main/java/com/demcha/examples/support/ShowcaseMetadata.java`. +- [ ] For minor releases (`vX.Y.0`): a migration note exists under `docs/migration/`. Patch releases skip this. ### D. Version artifacts (script-handled, agent verifies state) @@ -92,7 +93,7 @@ The script does **not** handle these. They are either pre-release or post-releas - **Stale documentation claims** — examples count, gallery descriptors, version-anchored prose. Fix in a `docs: pre-release fixes — ` commit on develop, then commit, then push (or stage and let the user push). - **CHANGELOG `## v — Planned` header** — must exist before the script runs. If you bumped scope mid-cycle, ensure the planned header is still on the right version line. -- **Missing migration guide** for minor releases — write `docs/migration-v1--to-v1-.md` if absent. +- **Missing migration guide** for minor releases — write the note under `docs/migration/` if absent. - **`InternalAnnotationCoverageTest` and other guard tests** — fix any failures by adjusting the source (annotation propagation, doc rewording), never by suppressing the test or extending the allowlist. Allowlist edits are reviewable evidence of an architecture decision; write or update an ADR before suppressing. ### 2.B Post-release (after `cut-release.ps1` succeeds and the tag is pushed) @@ -104,7 +105,7 @@ Run within 1 hour of the tag push. Independent steps can run in parallel. 3. **Merge `develop` → `main`** on GitHub so GitHub Pages picks up the new docs. Fast-forward only — never force-push `main`. If the push is rejected with `non-fast-forward`, a hotfix landed on `main` after the audit and the merge has to be redone after merging `origin/main` back into `develop`. 4. **Verify CI green on main** — `gh run list --branch main --limit 1` shows `success` for the tag commit. 5. **Smoke-test the install snippet** — minimal POM in `$env:TEMP`, `mvn dependency:resolve` against the snippet copy-pasted from README, expect 0 exit. -6. **Re-run all examples against the published artifact** — `./mvnw -f examples/pom.xml clean package` followed by `exec:java -Dexec.mainClass=com.demcha.examples.GenerateAllExamples`. Expect 26+ `Generated:` lines. +6. **Re-run all examples against the published artifact** — `./mvnw -f examples/pom.xml clean package` followed by `exec:java -Dexec.mainClass=com.demcha.examples.GenerateAllExamples`. Expect one `Generated:` line per example. 6b. **Run the external release-smoke suite** — once Central has indexed the train, dispatch the **Release Smoke** workflow ([`.github/workflows/release-smoke.yml`](../../.github/workflows/release-smoke.yml)) with `version=`, or run `bash scripts/release-smoke/run.sh --version `. This resolves every published coordinate from Maven Central in a clean, GraphCompose-evicted repository (no reactor / local install) and exercises the documented consumer scenarios — the wrapper renders PDF, `graph-compose-core` alone throws `MissingBackendException`, core+render-pdf renders, and templates/testing/bundle perform their roles. It is the authoritative "a real user can install and use this" check; the minimal step-5 snippet resolve is a faster subset. (Release smoke tests **published** artifacts, so it necessarily runs post-publish, not pre-tag.) 7. **Open the next development line** — `pwsh ./scripts/cut-release.ps1 -PostReleaseOnly`. This bumps the train poms to the next patch `-SNAPSHOT` (so develop builds are distinguishable from the release and the japicmp gate compares against it) **and** restores linkable "View Code" buttons by flipping ShowcaseMetadata back to `/blob/develop`. The README/showcase install snippets stay on the just-published release. 8. **GitHub Release — automated.** Pushing the `v` tag triggers [`.github/workflows/release.yml`](../../.github/workflows/release.yml): it re-runs `./mvnw clean verify` over the whole reactor against the tagged commit, then creates the Release with that version's CHANGELOG section as the body (hyphenated tags like `v1.7.0-rc.1` ship as pre-releases; the step is idempotent — it edits the notes if the Release already exists). The workflow titles it `GraphCompose v`; for a **minor** release, edit the title to add the codename (`v1.4`=cinematic, `v1.5`=intuitive, `v1.6`=expressive; patches drop it). Create the Release by hand (`gh release create v --notes-file `) only if the workflow is unavailable. diff --git a/examples/README.md b/examples/README.md index 27b908f50..fe9c7186d 100644 --- a/examples/README.md +++ b/examples/README.md @@ -15,7 +15,7 @@ Install the library artifact once from the repository root: ./mvnw -DskipTests install ``` -Then run all 63 examples in one shot: +Then run every example in one shot: ```bash ./mvnw -f examples/pom.xml exec:java \ @@ -32,7 +32,7 @@ Then run all 63 examples in one shot: Generated PDFs land in `examples/target/generated-pdfs/`. The same `mvnw.cmd` form works on Windows PowerShell with backslash paths. -`GenerateAllExamples` runs **63** example programs — 16 CV + 15 +`GenerateAllExamples` renders the whole catalogue in one pass — the CV and cover-letter presets plus invoices, proposals, a schedule, the feature demos, and the flagships. The showcase site surfaces the full generated catalogue (~53 PDFs); a curated 39-PDF subset is committed under diff --git a/scripts/cut-release.ps1 b/scripts/cut-release.ps1 index d84693c88..60ab7953e 100644 --- a/scripts/cut-release.ps1 +++ b/scripts/cut-release.ps1 @@ -234,15 +234,55 @@ function Get-NextSnapshotVersion($version) { } function Test-ReadmeLatestStable($version) { - # The README 'Latest stable' prose block (> ... **Latest stable**: [vX.Y.Z](...)) is a - # maintainer pre-cut edit that cut-release does NOT rewrite and no test guards. Returns - # $true when it names $version. Checked in Step 0 BEFORE any file mutation, so a stale - # line aborts the cut with a still-clean tree — a post-mutation check would leave a - # dirty tree that the next preflight run then refuses to work over. + # True when the README 'Latest stable' prose block names $version. Used to verify + # the block AFTER Step 1 rewrote it — see Update-ReadmeReleaseStatus. $readme = Get-Content (Join-Path $repoRoot 'README.md') -Raw return $readme -match "\*\*Latest stable\*\*:\s*\[v$([regex]::Escape($version))\]" } +function Update-ReadmeReleaseStatus($readmePath, $newVersion) { + # The README 'Release status' blockquote carries two halves: + # + # > 🟢 **Latest stable**: [vX.Y.Z](…/releases/tag/vX.Y.Z) — … + # > · 🟡 **In development**: vX.Y.(Z+1) on `develop` — … + # + # It used to be a maintainer pre-cut hand-edit, gated in Step 0. That forced a + # false state: to leave `main` correct after the tag, `develop` had to advertise + # an unpublished version as "latest stable" for the whole cycle, with a release + # link that 404s. The script owns the block now — Step 1 promotes the in-development + # half to latest-stable and opens the next patch line — so `develop` stays truthful + # between releases and the release commit still carries the right text to `main`. + # Only the version tokens are rewritten; the maintainer's prose is left alone. + if (-not (Test-Path $readmePath)) { + Note "skip (no file): $readmePath" + return + } + $content = Get-Content $readmePath -Raw + $next = Get-NextSnapshotVersion $newVersion + $nextVersion = if ($next) { $next -replace '-SNAPSHOT$', '' } else { $null } + + $stable = [regex]'(?<=\*\*Latest stable\*\*:\s*\[v)[\w\.\-]+(?=\])' + $stableUrl = [regex]'(?<=/releases/tag/v)[\w\.\-]+(?=\))' + $inDev = [regex]'(?<=\*\*In development\*\*:\s*v)[\w\.\-]+' + + $updated = $stable.Replace($content, $newVersion, 1) + $updated = $stableUrl.Replace($updated, $newVersion, 1) + if ($nextVersion) { + $updated = $inDev.Replace($updated, $nextVersion, 1) + } + + if ($updated -eq $content) { + Note "README release-status block already names v$newVersion" + return + } + if ($DryRun) { + Note "[DRY RUN] README release status -> latest stable v$newVersion, in development v$nextVersion" + return + } + [System.IO.File]::WriteAllText($readmePath, $updated) + Note "README release status -> latest stable v$newVersion, in development v$nextVersion" +} + function Assert-ReleaseMetadata($version, $isFinalRelease) { # Fast, build-free validation of the metadata cut-release itself rewrote in Steps 1-2. # Runs at Step 2b — after those mutations, before commit/tag; VersionConsistencyGuardTest @@ -271,6 +311,13 @@ function Assert-ReleaseMetadata($version, $isFinalRelease) { } elseif ($snippet.Groups[1].Value -ne $version) { $problems += "README.md install snippet is $($snippet.Groups[1].Value), expected $version." } + + # 2b. README release-status block names this version as latest stable (Step 1 + # rewrites it; this is the post-mutation verification that replaced the old + # pre-cut hand-edit gate). + if (-not (Test-ReadmeLatestStable $version)) { + $problems += "README.md release-status block does not name v$version as 'Latest stable'." + } } # 3. Every train pom's own is this version (belt-and-suspenders ahead @@ -779,19 +826,17 @@ try { } Note ("tag {0}: available OK" -f $tag) - # README 'Latest stable' prose block must already name the target — FINAL releases - # only. Checked HERE, before Step 1 mutates any file, so a stale line aborts with a - # still-clean tree. (cut-release does not rewrite this block; it is a maintainer - # pre-cut edit.) A pre-release never becomes 'Latest stable', so there is nothing to - # check for it. + # The README release-status block is rewritten by Step 1 (Update-ReadmeReleaseStatus) + # and verified after the mutation by Assert-ReleaseMetadata, so there is nothing to + # demand of it here. What Step 0 still checks is that the block EXISTS in the shape + # the rewrite expects — a renamed or reflowed blockquote would otherwise be skipped + # in silence and ship `main` advertising the previous release, the v1.6.9 failure. if ($isFinalRelease) { - if (Test-ReadmeLatestStable $Version) { - Note "README 'Latest stable' = v$Version OK" - } elseif ($DryRun) { - Write-Host " [DRY RUN] WARNING: README 'Latest stable' does not name v$Version - update it on $Branch before the real cut." -ForegroundColor Yellow - } else { - throw "README 'Latest stable' block does not name v$Version. Update it on $Branch before cutting (cut-release.ps1 does not rewrite this block)." + $readmeRaw = Get-Content (Join-Path $repoRoot 'README.md') -Raw + if ($readmeRaw -notmatch '\*\*Latest stable\*\*:\s*\[v[\w\.\-]+\]') { + throw "README has no '**Latest stable**: [vX.Y.Z](...)' release-status line for the cut to rewrite." } + Note "README release-status block present OK" } if ($isFinalRelease) { @@ -849,6 +894,7 @@ try { # latest published release for a non-final (SNAPSHOT or pre-release) working version. if ($isFinalRelease) { Update-ReadmeInstallVersion (Join-Path $repoRoot 'README.md') $Version + Update-ReadmeReleaseStatus (Join-Path $repoRoot 'README.md') $Version # Per-module README install snippets (train modules only — fonts/emoji pin # their own independent versions). foreach ($moduleReadme in @('core/README.md', 'render-pdf/README.md', 'render-docx/README.md', diff --git a/web/index.html b/web/index.html index 33b4cd3f2..60f5ef81c 100644 --- a/web/index.html +++ b/web/index.html @@ -3,7 +3,7 @@ - + GraphCompose — Java PDF layout engine for structured business documents @@ -30,7 +30,7 @@ - + @@ -94,8 +94,7 @@ { "@type": "ItemList", "name": "GraphCompose showcase examples", - "description": "50+ runnable Java examples that produce real PDFs — CV templates, cover letters, invoices, proposals, schedules, plus every supported feature.", - "numberOfItems": 51, + "description": "Runnable Java examples that produce real PDFs — CV templates, cover letters, invoices, proposals, schedules, plus every supported feature.", "itemListElement": [ { "@type": "ListItem", @@ -263,7 +262,7 @@

30-second hello world

-

50+ generated PDFs you can inspect.

+

Generated PDFs you can inspect.

Every example below is a real, runnable Java file. Click the preview to open the rendered PDF, or jump straight into the source.