diff --git a/docs/README.md b/docs/README.md index 33d23e7d4..56b023b30 100644 --- a/docs/README.md +++ b/docs/README.md @@ -13,7 +13,8 @@ back here. | You are… | Read | |---|---| -| **New to GraphCompose** — what is it, how do I render my first PDF | [Getting started](getting-started.md) → [Hello world in root README](../README.md#hello-world) | +| **New to GraphCompose** — what is it, how do I render my first PDF | [Your first document](first-document.md) → [Getting started](getting-started.md) | +| **Author rendering an invoice or proposal** | [Built-in business templates](templates/business-templates.md) | | **Author rendering a CV** with your own data | [Templates v2 (layered) — quickstart](templates/v2-layered/quickstart.md) | | **Designer / author** wanting a custom visual style for CVs | [Templates v2 (layered) — authoring presets](templates/v2-layered/authoring-presets.md) | | **Author using legacy v1.6 templates** (CV / cover-letter / invoice / proposal still using `*Spec` + builders) | [Templates v1-classic — landing](templates/v1-classic/README.md) | @@ -26,10 +27,14 @@ back here. ## 📁 By category ### Getting started +- **[first-document.md](first-document.md)** — the five-minute path from an empty project to a rendered PDF. - **[getting-started.md](getting-started.md)** — DSL vs templates, first-render walk-through, decision tree. +- **[capabilities.md](capabilities.md)** — one-glance map of every feature with its stability tier and guide link. +- **[diagrams.md](diagrams.md)** — visual decision diagrams (authoring path, layout, output, lifecycle). - **[troubleshooting.md](troubleshooting.md)** — symptom-first fixes for common gotchas: stray `?` glyphs, silent DOCX drops, optional-dependency `NoClassDefFoundError`, running the bundled examples. ### Templates +- **[templates/business-templates.md](templates/business-templates.md)** — built-in invoice & proposal templates: the compose-first contract, end to end. - **[templates/v2-layered/](templates/v2-layered/)** — 🆕 canonical going-forward pattern (CV is the reference implementation): `data` / `theme` / `components` / `widgets` / `presets`. - **[templates/v1-classic/](templates/v1-classic/)** — the spec/builder/presets surface used by v1.6 CV, cover-letter, invoice, proposal templates. Still ships, still supported. diff --git a/docs/capabilities.md b/docs/capabilities.md new file mode 100644 index 000000000..0aa109414 --- /dev/null +++ b/docs/capabilities.md @@ -0,0 +1,89 @@ +# Capabilities + +A one-glance map of what GraphCompose can do, the main API for each, +and its stability tier. "Stability" rows use the tiers defined in the +[API stability policy](api-stability.md); "Guide" links to the page that +shows it in context. + +This is a feature catalogue, not the contract — the +[API stability policy](api-stability.md) is authoritative for what each +tier promises, and [canonical ⇄ legacy parity](architecture/canonical-legacy-parity.md) +tracks what is `Partial` or `Planned`. + +--- + +## Authoring + +| Capability | Main API | Stability | Guide | +|---|---|---|---| +| Open a document session | `GraphCompose.document(...)` → `DocumentSession` | Stable | [Your first document](first-document.md) | +| Describe content in reading order | `pageFlow(...)`, `module(...)`, `addSection(...)` | Stable | [Getting started](getting-started.md) | +| Maintained document templates | `InvoiceTemplateV2`, `ProposalTemplateV2`, `cv.v2.*`, `coverletter.v2.*` | Stable | [Templates](templates/which-template-system.md) | +| Reusable building blocks (helpers) | helper methods / widgets over the DSL | Stable | [Diagrams](diagrams.md#choose-your-authoring-path) | +| Custom node / backend | `NodeDefinition`, render-handler SPI, `FixedLayoutBackend` | Extension SPI (`@Beta`) | [Extending](recipes/extending.md) | + +## Content + +| Capability | Main API | Stability | Guide | +|---|---|---|---| +| Paragraphs & rich inline text | `addParagraph(...)`, `addRich(...)`, `RichText` | Stable | [Recipes — rich text](recipes.md) | +| Lists (flat & nested) | `addList(...)`, `ListBuilder` | Stable | [Recipes](recipes.md) | +| Tables (spans, zebra, totals, repeat header) | `addTable(...)`, `DocumentTableCell` | Stable | [Advanced tables](recipes/tables.md) | +| Raster images | `addImage(...)`, fit modes | Stable | [Shapes & images](recipes/shapes.md) | +| Vector shapes, dividers, lines | `addShape(...)`, `addLine(...)`, `addEllipse(...)` | Stable | [Shapes](recipes/shapes.md) | +| Charts (bar / line / pie) | `chart(ChartSpec...)`, `ChartData` | Stable | [Charts](recipes/charts.md) | +| Barcodes & QR | `addBarcode(...)` | Stable | [Recipes](recipes.md) | + +## Layout + +| Capability | Main API | Stability | Guide | +|---|---|---|---| +| Columns that still flow | `addRow(row -> row.weights(...))` | Stable | [Layered page design](recipes/layered-page-design.md) | +| Page-wide fills / bands | `pageBackground(...)`, `pageBackgrounds(...)` | Stable | [Page backgrounds](recipes/page-backgrounds.md) | +| Overlap & alignment | `addLayerStack(...)` | Stable | [Layered page design](recipes/layered-page-design.md) | +| Shape-as-container | `addContainer(...)`, `addCircle(...)`, `addEllipse(...)` | Stable | [Shape as container](recipes/shape-as-container.md) | +| Fixed (x, y) placement | `addCanvas(w, h, canvas -> canvas.position(...))` | Stable | [Absolute placement](recipes/absolute-placement.md) | +| Bleed to page edge | `bleedToEdge(...)` | Stable | [Page backgrounds](recipes/page-backgrounds.md) | +| Transforms (rotate / scale) | `DocumentTransform` | Stable | [Transforms](recipes/transforms.md) | + +## Output & testing + +| Capability | Main API | Stability | Guide | +|---|---|---|---| +| Write a PDF file | `buildPdf()`, `buildPdf(Path)` | Stable | [Getting started](getting-started.md) | +| Stream to a caller-owned stream | `writePdf(OutputStream)` | Stable | [Streaming](recipes/streaming.md) | +| In-memory bytes | `toPdfBytes()` | Stable | [Getting started](getting-started.md) | +| Editable Word (semantic) | `export(new DocxSemanticBackend())` | Stable (semantic, not PDF parity) | [Troubleshooting](troubleshooting.md) | +| PDF chrome (metadata / watermark / header / footer / protection) | `metadata(...)`, `watermark(...)`, `header(...)`, `footer(...)`, `protect(...)` | Stable | [Getting started](getting-started.md) | +| Layout snapshot regression | `LayoutSnapshotAssertions.assertMatches(...)` | Stable | [Layout snapshot testing](operations/layout-snapshot-testing.md) | +| Visual (pixel) regression | `PdfVisualRegression` | Stable | [Layout snapshot testing](operations/layout-snapshot-testing.md) | +| Render-only debug overlays | `guideLines(...)`, `debug(...)` | Stable | [Getting started](getting-started.md#debug-guide-lines) | + +## Navigation + +| Capability | Main API | Stability | Guide | +|---|---|---|---| +| External links | `addLink(...)`, `inlineLink(...)` | Stable | [Getting started](getting-started.md) | +| Internal jumps | `anchor("x")` + `linkTo("x")` | Stable | [Getting started](getting-started.md) | +| PDF outline bookmarks | `bookmark(new DocumentBookmarkOptions(...))` | Stable | [Getting started](getting-started.md) | + +--- + +## New in 1.9.0 + +These ship from 1.9.0 onward — confirm your dependency version before relying on them: + +| Capability | Main API | +|---|---| +| Printed page references | `addPageReference("anchor")` | +| Generated Table of Contents | `addTableOfContents(toc -> toc.entry(...))` | +| Page preview images | `toImage(pageIndex, dpi)`, `toImages(dpi)` | + +--- + +## See also + +- [API stability policy](api-stability.md) — what each tier promises. +- [Decision diagrams](diagrams.md) — visual "which API do I use?". +- [Recipes](recipes.md) — the full cookbook. +- [Which template system should I use?](templates/which-template-system.md) — template-surface decision. diff --git a/docs/diagrams.md b/docs/diagrams.md new file mode 100644 index 000000000..927339dd2 --- /dev/null +++ b/docs/diagrams.md @@ -0,0 +1,89 @@ +# Decision Diagrams + +Visual versions of the "which API do I reach for?" decisions. Each +diagram renders on GitHub (Mermaid). The prose walkthroughs live in +[Getting started](getting-started.md) and +[Your first document](first-document.md). + +--- + +## Choose your authoring path + +Start from intent. Most documents are either a maintained template or a +custom page flow; helpers, layout primitives, and extensions come later. + +```mermaid +flowchart TD + A[I want to generate a document] --> B{Known family?
CV / invoice / proposal / cover letter} + B -- Yes --> T[Use a maintained template] + B -- No --> C{Mostly reads top to bottom?} + C -- Yes --> F["GraphCompose.document(...) + pageFlow(...)"] + C -- Needs specific placement --> L["A layout primitive
(row / canvas / shape container)"] + F --> D{Same shape repeats across your app?} + D -- Yes --> H[Extract a helper / widget over the DSL] + D -- No --> Done1[Author the page flow] + H --> Done1 + L --> Done2[See the layout diagram below] + T --> Done3[Supply the data spec, then render] +``` + +--- + +## Where does content go on the page? + +Flow is the default. Reach for a stronger primitive only when the +content has a specific placement relationship. + +```mermaid +flowchart TD + A[Place content on the page] --> B{Top-to-bottom reading order?} + B -- Yes --> Flow[pageFlow / sections / modules] + B -- No --> C{Side by side, still part of the flow?} + C -- Yes --> Row["addRow(row -> row.weights(...))"] + C -- No --> D{A fill behind every page?} + D -- Yes --> BG["pageBackground(s)(...)"] + D -- No --> E{Overlap or a framed block?} + E -- Yes --> Layer[Layer stack / shape container] + E -- No --> Canvas["addCanvas(w, h, ...) — exact x/y"] +``` + +--- + +## Where does the output go? + +Choose the output method by destination. Create one `DocumentSession` +per render request. + +```mermaid +flowchart TD + A[The document is built] --> B{Where does it go?} + B -- A file --> F["buildPdf() / buildPdf(path)"] + B -- HTTP / cloud stream --> S["writePdf(OutputStream)"] + B -- A byte array --> Y["toPdfBytes()"] + B -- A preview image --> I["toImage(...) / toImages(...)"] + B -- Editable Word --> D["export(new DocxSemanticBackend())"] +``` + +--- + +## Document lifecycle + +What happens between your code and the PDF. + +```mermaid +flowchart LR + A["GraphCompose.document(...)"] --> B[DocumentSession] + B --> C["pageFlow(...) → semantic nodes"] + C --> D[Layout + pagination] + D --> E[PDF output] + D -. inspect .-> S["layoutSnapshot() (testing)"] +``` + +--- + +## See also + +- [Getting started](getting-started.md) — the prose decision tree and first render. +- [Your first document](first-document.md) — a five-minute walk-through. +- [Which template system should I use?](templates/which-template-system.md) — the template-surface decision. +- [Capabilities](capabilities.md) — what GraphCompose can do, with stability tiers. diff --git a/docs/first-document.md b/docs/first-document.md new file mode 100644 index 000000000..56666d72e --- /dev/null +++ b/docs/first-document.md @@ -0,0 +1,121 @@ +# Your First Document + +A five-minute path from an empty project to a real PDF. GraphCompose is +session-first: you open a `DocumentSession`, describe content in reading order +with a page flow, and render. No coordinates, no manual page breaks. + +> **Prerequisites:** Java 17+ and the `io.github.demchaav:graph-compose` +> dependency — see the [README install snippet](../README.md#installation). + +## The smallest document + +Open a session for a file path, add one page flow, render. The engine handles +placement and pagination. + +```java +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentPageSize; +import com.demcha.compose.document.api.DocumentSession; + +import java.nio.file.Path; + +try (DocumentSession document = GraphCompose.document(Path.of("hello.pdf")) + .pageSize(DocumentPageSize.A4) + .margin(24, 24, 24, 24) + .create()) { + + document.pageFlow(page -> page + .module("Summary", module -> module.paragraph("Hello GraphCompose"))); + + document.buildPdf(); +} +``` + +`GraphCompose.document(path)` configures the output; `create()` returns the +`DocumentSession`. Use try-with-resources so the session is always released, even +if rendering fails. Inside the session, `pageFlow(...)` is the document body: +modules, sections, paragraphs, lists, tables, and rows are added top to bottom. + +## A real custom document + +The same Flow model scales to a multi-section document. There are still no +coordinates and no manual page breaks — just structure in reading order. + +```java +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentPageSize; +import com.demcha.compose.document.api.DocumentSession; + +import java.nio.file.Path; + +try (DocumentSession document = GraphCompose.document(Path.of("profile.pdf")) + .pageSize(DocumentPageSize.A4) + .margin(24, 24, 24, 24) + .create()) { + + document.pageFlow() + .name("CandidateProfile") + .spacing(12) + .module("Professional Summary", module -> module.paragraph( + "Backend engineer focused on clean Java APIs, stable document " + + "output, and reusable template architecture.")) + .module("Technical Skills", module -> module.bullets( + "Java 21 and Spring Boot", + "PDF document generation with GraphCompose", + "Layout snapshot testing and render regression checks")) + .module("Projects", module -> module.rows( + "GraphCompose - declarative document layout engine.", + "CVRewriter - profile-aware CV tailoring platform.")) + .build(); + + document.buildPdf(); +} +``` + +The callback form (`pageFlow(page -> ...)`) builds and attaches the root for you. +The builder form (`pageFlow().…build()`) gives you the fluent chain but you must +call `.build()` yourself. + +## Already a known document? Use a template + +If your document is a known family — invoice, proposal, CV, cover letter — do not +hand-build it. A maintained template maps a typed data object into the same +session, then you render as usual: + +```java +import com.demcha.compose.document.templates.builtins.InvoiceTemplateV2; +import com.demcha.compose.document.theme.BusinessTheme; + +InvoiceTemplateV2 template = new InvoiceTemplateV2(BusinessTheme.modern()); + +try (DocumentSession document = GraphCompose.document(Path.of("invoice.pdf")).create()) { + template.compose(document, invoice); // invoice = your InvoiceDocumentSpec + document.buildPdf(); +} +``` + +Templates and hand-written Flow compose into the *same* `DocumentSession`, so you +can mix them. To choose a template surface, see +[Which template system should I use?](templates/which-template-system.md). + +## Rendering on a server + +When the caller already owns the output stream — an HTTP response, a cloud +upload — create the session *without* a default path and stream the PDF with +`writePdf(OutputStream)` instead of `buildPdf()`. GraphCompose writes the +stream but does not close it. For the full server snippet, see +[Getting started — Streaming output](getting-started.md#streaming-output). + +Create one `DocumentSession` per render request; it is mutable and not +thread-safe. Use `toPdfBytes()` only when the caller truly needs a byte array. + +## Where to go next + +- [Getting Started](getting-started.md) — themes, hero blocks, layer stacks, + shape-as-container, and built-in templates. +- [Recipes](recipes.md) — themes, shapes, transforms, tables, and layout + snapshots. +- [Which template system should I use?](templates/which-template-system.md) — + the decision tree for CV / invoice / proposal surfaces. +- [Production Rendering](operations/production-rendering.md) — server-side + lifecycle, streaming, and load guidance. diff --git a/docs/getting-started.md b/docs/getting-started.md index 22de720cd..ded4d78ec 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -7,6 +7,8 @@ with `writePdf(...)`, `buildPdf()`, or `toPdfBytes()`. > **Prerequisites:** Java 17+ and the `io.github.demchaav:graph-compose` dependency — see the [README install snippet](../README.md#installation). +> **New to GraphCompose?** Start with [Your First Document](first-document.md) — a five-minute, copy-paste path from an empty project to a rendered PDF, then come back here for themes, layer stacks, and built-in templates. + ## Templates vs DSL — pick the right starting point GraphCompose has two layers a caller can target. Use this decision diff --git a/docs/templates/business-templates.md b/docs/templates/business-templates.md new file mode 100644 index 000000000..29c843ace --- /dev/null +++ b/docs/templates/business-templates.md @@ -0,0 +1,179 @@ +# Built-In Business Templates — Invoice & Proposal + +GraphCompose ships maintained templates for two common business +documents: **invoices** and **proposals**. You supply a typed data +spec, pick a `BusinessTheme`, and the template renders a consistent, +branded document. You never position anything by hand. + +> For **CVs and cover letters**, use the layered `cv.v2` / `coverletter.v2` +> model instead — see the [Templates v2 (layered) quickstart](v2-layered/quickstart.md). +> Not sure which surface to target? See +> [Which template system should I use?](which-template-system.md). + +## The compose-first contract + +Every built-in template follows the same five steps. The template owns +the document structure; your application owns the data and the output +destination. + +1. Build a data spec — `InvoiceDocumentSpec` or `ProposalDocumentSpec`. +2. Choose a `BusinessTheme` (commonly `BusinessTheme.modern()`). +3. Create the template with the theme. +4. Open a `DocumentSession`. +5. `template.compose(document, spec)`, then render. + +The template composes into an **open** `DocumentSession` — it never +decides file vs stream vs bytes. The caller does. + +## Invoice + +```java +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentPageSize; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.builtins.InvoiceTemplateV2; +import com.demcha.compose.document.templates.data.invoice.InvoiceDocumentSpec; +import com.demcha.compose.document.theme.BusinessTheme; + +import java.nio.file.Path; + +InvoiceDocumentSpec invoice = InvoiceDocumentSpec.builder() + .title("Invoice") + .invoiceNumber("GC-2026-041") + .issueDate("25 Jun 2026") + .dueDate("25 Jul 2026") + .reference("GraphCompose implementation") + .status("Due") + .fromParty(party -> party + .name("GraphCompose Studio") + .addressLines("10 Example Street", "London") + .email("billing@example.com") + .phone("+44 20 0000 0000") + .taxId("VAT GB000000000")) + .billToParty(party -> party + .name("Client Ltd") + .addressLines("22 Client Road", "Manchester") + .email("accounts@client.example")) + .lineItem("Document engine integration", "Implementation and support", + "1", "4,800.00", "4,800.00") + .summaryRow("Subtotal", "4,800.00") + .summaryRow("VAT", "960.00") + .totalRow("Total", "5,760.00") + .paymentTerm("Payment due within 30 days.") + .footerNote("Thank you for your business.") + .build(); + +BusinessTheme theme = BusinessTheme.modern(); +InvoiceTemplateV2 template = new InvoiceTemplateV2(theme); + +try (DocumentSession document = GraphCompose.document(Path.of("invoice.pdf")) + .pageSize(DocumentPageSize.A4) + .pageBackground(theme.pageBackground()) + .margin(28, 28, 28, 28) + .create()) { + template.compose(document, invoice); + document.buildPdf(); +} +``` + +The template renders a masthead with the invoice metadata, two-column +seller / buyer blocks, a zebra-striped line-item table with summary and +total rows, and a notes / payment-terms footer. + +## Proposal + +Same shape, different spec. Use a proposal when the artifact is sales or +project scope rather than billing. The timeline takes a three-argument +`timelineItem(phase, duration, details)`. + +```java +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentPageSize; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.builtins.ProposalTemplateV2; +import com.demcha.compose.document.templates.data.proposal.ProposalDocumentSpec; +import com.demcha.compose.document.theme.BusinessTheme; + +import java.nio.file.Path; + +ProposalDocumentSpec proposal = ProposalDocumentSpec.builder() + .title("Proposal") + .proposalNumber("PR-2026-014") + .preparedDate("25 Jun 2026") + .validUntil("25 Jul 2026") + .projectTitle("Document Automation Platform") + .executiveSummary("A proposal for building reliable PDF generation into the product workflow.") + .sender(party -> party + .name("GraphCompose Studio") + .addressLines("10 Example Street", "London") + .email("hello@example.com") + .website("graphcompose.example")) + .recipient(party -> party + .name("Client Ltd") + .addressLines("22 Client Road", "Manchester") + .email("product@client.example")) + .section("Scope", "Backend integration, template setup, and regression checks.") + .timelineItem("Week 1", "1 week", "Data model and rendering endpoint.") + .pricingRow("Implementation", "Fixed scope", "4,800.00") + .emphasizedPricingRow("Total", "Excluding tax", "4,800.00") + .acceptanceTerm("Proposal valid for 30 days.") + .footerNote("Prepared with GraphCompose.") + .build(); + +BusinessTheme theme = BusinessTheme.modern(); +ProposalTemplateV2 template = new ProposalTemplateV2(theme); + +try (DocumentSession document = GraphCompose.document(Path.of("proposal.pdf")) + .pageSize(DocumentPageSize.A4) + .pageBackground(theme.pageBackground()) + .margin(28, 28, 28, 28) + .create()) { + template.compose(document, proposal); + document.buildPdf(); +} +``` + +## Rendering on a server + +In production the spec usually comes from application data and the +document is streamed to the caller's stream. The template composes the +same way before any output method; create one session per request. + +```java +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.builtins.InvoiceTemplateV2; +import com.demcha.compose.document.templates.data.invoice.InvoiceDocumentSpec; +import com.demcha.compose.document.theme.BusinessTheme; + +import java.io.OutputStream; + +void streamInvoice(InvoiceDocumentSpec invoice, OutputStream out) throws Exception { + InvoiceTemplateV2 template = new InvoiceTemplateV2(BusinessTheme.modern()); + + try (DocumentSession document = GraphCompose.document().create()) { + template.compose(document, invoice); + document.writePdf(out); + } +} +``` + +## Customizing + +If the built-in structure is close but not exact, prefer these moves in +order: + +1. Check whether the spec already has the field you need. +2. Change the `BusinessTheme` (or its tokens) for branding. +3. Wrap the template call with session-level PDF chrome — a footer, + metadata, or protection — see [Getting started](../getting-started.md). +4. Only fork or write a new template when the document *structure* itself + differs. A custom template implements the same `DocumentTemplate` + contract and composes into the same session. + +## See also + +- [Which template system should I use?](which-template-system.md) — the full decision tree. +- [Templates v2 (layered) quickstart](v2-layered/quickstart.md) — CVs and cover letters. +- [Recipes — themes](../recipes/themes.md) — customizing `BusinessTheme`. +- [Streaming](../recipes/streaming.md) — server output patterns.