diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3cabb7df7..ebcf46633 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -274,7 +274,7 @@ jobs: cache: maven - name: Restore benchmark history cache - uses: actions/cache@v5 + uses: actions/cache@v6 with: path: benchmarks/target/benchmarks/current-speed key: benchmark-current-speed-${{ github.ref_name }}-${{ github.run_id }} diff --git a/.github/workflows/deploy-web.yml b/.github/workflows/deploy-web.yml index 37c5dc1f8..5335b4ff7 100644 --- a/.github/workflows/deploy-web.yml +++ b/.github/workflows/deploy-web.yml @@ -10,19 +10,19 @@ name: Deploy web showcase to GitHub Pages # (one-time switch from "Deploy from a branch"). Until that switch is made, # this workflow builds the artifact but Pages keeps serving the old source. # -# Triggers: every push to `main` AND every `v*` release tag. The tag trigger -# is the reliable one for releases — the v1.8.0 release fast-forwarded `main` -# with 67 changed `web/` files yet the old `paths: web/**` filter silently -# skipped the deploy, so the live site kept serving the previous release. We -# dropped the paths filter rather than depend on it: `main` is only pushed at -# releases / hotfixes, so deploying unconditionally is cheap and correct, and -# the `v*` tag is a second guaranteed trigger. `concurrency` de-dupes the two -# triggers a release fires. +# Triggers: every push to `main` (no `paths` filter). `main` is only pushed at +# releases / hotfixes, so deploying unconditionally is cheap and correct — the +# v1.8.0 release fast-forwarded `main` with 67 changed `web/` files yet the old +# `paths: web/**` filter silently skipped the deploy, leaving the live site on +# the previous release. A `v*` tag trigger was tried as a backup but removed: +# GitHub Pages refuses to deploy from a tag ref (the `github-pages` environment +# only allows `main`), so it failed every release without ever deploying — and +# via `cancel-in-progress` could even cancel the good `main` run. The +# unconditional `main` trigger covers releases reliably on its own. on: push: branches: [main] - tags: ["v*"] workflow_dispatch: permissions: diff --git a/CHANGELOG.md b/CHANGELOG.md index 782755d47..9a7cddcfe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ All notable changes to GraphCompose are documented here. Versions follow semantic versioning; release dates are ISO 8601. -## v1.9.0 — unreleased +## v1.9.0 — 2026-06-29 In-document navigation. Rendered PDFs can now declare named **anchors** and **internal links** that jump to them — clickable tables of contents, @@ -305,6 +305,18 @@ PDF `GoTo` actions. External links are unchanged. — a paginated catalogue of the entire bundled emoji set (every indexed glyph, drawn inline). +### Build + +- **The README hero banner is now version-stamped and re-rendered on release.** + `EngineDeckExample` reads its version and codename from a filtered + `banner.properties` (`@project.version@`) instead of hardcoded constants, and + the new `ReadmeBannerRenderer` writes + `assets/readme/repository_showcase_render.png` straight from the engine via + `DocumentSession.toImage(...)` — no PDF-rasterize round-trip. + `cut-release.ps1` re-renders and stages the hero on every tag, and + `VersionConsistencyGuardTest` fails the build if the banner version is ever + hardcoded again. + ### Tests - `InternalLinkAnchorTest` (PDFBox assertions): forward and backward references diff --git a/README.md b/README.md index 66e97b8ce..fb40339be 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@

> **Release status** — -> 🟢 **Latest stable**: [v1.8.0](https://github.com/DemchaAV/GraphCompose/releases/tag/v1.8.0) — codenamed **"illustrative"**: native vector charts, SVG & gradient graphics, free-form clipping, and a leaner engine artifact. **[What's new in v1.8 ↓](#whats-new-in-v18)** +> 🟢 **Latest stable**: [v1.9.0](https://github.com/DemchaAV/GraphCompose/releases/tag/v1.9.0) — codenamed **"navigable"**: in-document navigation (anchors, internal links, a clickable TOC, page references & bookmarks), multi-section documents, and inline chips / SVG icons / colour emoji. **[What's new in v1.9 ↓](#whats-new-in-v19)** >  ·  🟡 **In develop**: next cycle — open (see [CHANGELOG](./CHANGELOG.md)) >  ·  See [API stability policy](./docs/api-stability.md) for tier definitions. @@ -51,22 +51,19 @@ Sits between **iText** (low-level page primitives) and **JasperReports** (XML-template-driven layout): a Java DSL describes the document semantically, the engine renders. -## What's new in v1.8 +## What's new in v1.9 -The **"illustrative"** release — the engine gains a vector-graphics dimension. +The **"navigable"** release — a rendered PDF becomes a document you can move through. -

- GraphCompose native vector charts -

- -- **Native vector charts** — bar / line / pie, inline sparklines, and `MONOTONE` / `SMOOTH` line interpolation, drawn as native PDF Béziers (no rasterization): `section.chart(ChartSpec.line()…, style)`. -- **SVG path & icon import** — `SvgIcon.parse(svg)` turns SVG into native vector geometry; recolour per use and place with `addSvgIcon(icon, width, align)`. -- **Gradients & free-form clipping** — linear / radial `DocumentPaint` fills and arbitrary `ShapeOutline.Path` clip regions. -- **Block-level alignment** — `addAligned(HorizontalAlign.CENTER, node)` centres or right-aligns any fixed node without a wrapper container. -- **`keepTogether()` pagination** — keep a section from splitting across a page break. -- **Leaner publication** — the bundled Google fonts moved to the independently-versioned `graph-compose-fonts` artifact, so the engine jar dropped from ~20 MB to ~2 MB. Pure-text / standard-14 documents need nothing extra; add `graph-compose-fonts` (or `graph-compose-bundle`) to keep the bundled families — see the [migration note](./docs/migration/v1.8.0-fonts.md). +- **In-document navigation** — named `anchor(...)` targets and internal `link(...)` jumps emitted as native PDF `GoTo` actions: clickable cross-references, `[text](#heading)`-style links, and bidirectional footnotes (`DocumentLinkTarget` unifies internal and external links). +- **Native table of contents & page references** — `addTableOfContents(toc -> toc.entry(label, anchor))` builds a clickable TOC with dot leaders and auto-resolved page numbers; `addPageReference(anchor)` prints a native "see page N" cross-reference; `DocumentSession.pageIndex()` resolves any anchor to its page. +- **Bookmarks & viewer preferences** — `section.bookmark(...)` makes any section or container a PDF outline (bookmark-panel) target, and `chrome().viewerPreferences(...)` opens the reader on the outline panel, a chosen page layout, or the doc title in the window. +- **Multi-section documents** — `GraphCompose.documents()` concatenates independently authored sections — each with its own page size, margins, and footer numbering — into one PDF, with anchors, links, and the outline resolving across section boundaries. +- **Richer row & page layout** — `row.columns(auto(), weight(1), fixed(80))`, main-axis `flexSpacer()` / `arrangement(...)`, cross-axis `verticalAlign(...)`, per-page `pageMargins(...)`, and full-bleed `bleed(...)`. +- **Inline chips, SVG icons & colour emoji** — text on a rounded highlight chip, a parsed `SvgIcon` on the text baseline, and `RichText.emoji(":star:", size)` colour emoji via the new independently-versioned `graph-compose-emoji` module. +- **Render straight to images** — `DocumentSession` renders directly to `BufferedImage`s with no PDF round-trip; plus page-number offset / restart / style and round / dotted line caps. -Core document APIs stay source- and binary-compatible with v1.7 (`ConfigLoader` is the one removal). Full notes in [`CHANGELOG.md`](./CHANGELOG.md). +Core document APIs stay source- and binary-compatible with v1.8 — v1.9 is purely additive (two cover-letter / CV shim types are newly `@Deprecated` for 2.0). Full notes in [`CHANGELOG.md`](./CHANGELOG.md). ## Installation @@ -76,12 +73,12 @@ Core document APIs stay source- and binary-compatible with v1.7 (`ConfigLoader` io.github.demchaav graph-compose - 1.8.0 + 1.9.0 ``` ```kotlin -dependencies { implementation("io.github.demchaav:graph-compose:1.8.0") } +dependencies { implementation("io.github.demchaav:graph-compose:1.9.0") } ``` > **Bundled fonts (from v1.8.0).** The curated Google fonts no longer ship @@ -103,6 +100,23 @@ dependencies { implementation("io.github.demchaav:graph-compose:1.8.0") } > above) to pull the engine + fonts together. Full details and upgrade steps: > the [v1.8.0 fonts migration note](./docs/migration/v1.8.0-fonts.md). +> **Colour emoji (from v1.9.0).** `RichText.emoji(":star:", size)` resolves +> GitHub-style shortcodes to inline vector glyphs from an independently-versioned +> companion artifact (the same split model as the fonts above). Text without +> emoji needs nothing extra; to render colour emoji, add: +> +> ```xml +> +> io.github.demchaav +> graph-compose-emoji +> 1.0.0 +> +> ``` +> +> An unknown shortcode falls back to its literal text, so a document that uses no +> emoji — or runs without the artifact — renders unchanged. The +> `graph-compose-bundle` stays fonts-only; emoji is opt-in. + > **Distribution** — Maven Central is the canonical channel from **v1.6.6** onwards > (`io.github.demchaav:graph-compose:`). Hosted Javadocs auto-publish to > [javadoc.io/doc/io.github.demchaav/graph-compose](https://javadoc.io/doc/io.github.demchaav/graph-compose) diff --git a/aggregator/pom.xml b/aggregator/pom.xml index 2f7fbcc42..45bc81a67 100644 --- a/aggregator/pom.xml +++ b/aggregator/pom.xml @@ -6,7 +6,7 @@ io.github.demchaav graph-compose-build - 1.8.0 + 1.9.0 pom GraphCompose Build Aggregator diff --git a/assets/readme/examples/emoji-shortcodes.pdf b/assets/readme/examples/emoji-shortcodes.pdf new file mode 100644 index 000000000..32910dedb Binary files /dev/null and b/assets/readme/examples/emoji-shortcodes.pdf differ diff --git a/assets/readme/examples/in-pdf-navigation.pdf b/assets/readme/examples/in-pdf-navigation.pdf new file mode 100644 index 000000000..5a9363dd2 Binary files /dev/null and b/assets/readme/examples/in-pdf-navigation.pdf differ diff --git a/assets/readme/examples/inline-highlight-chips.pdf b/assets/readme/examples/inline-highlight-chips.pdf new file mode 100644 index 000000000..51018b0be Binary files /dev/null and b/assets/readme/examples/inline-highlight-chips.pdf differ diff --git a/assets/readme/examples/inline-svg-icons.pdf b/assets/readme/examples/inline-svg-icons.pdf new file mode 100644 index 000000000..546f65db4 Binary files /dev/null and b/assets/readme/examples/inline-svg-icons.pdf differ diff --git a/assets/readme/repository_showcase_render.png b/assets/readme/repository_showcase_render.png index 7617ab077..1a4a62fa3 100644 Binary files a/assets/readme/repository_showcase_render.png and b/assets/readme/repository_showcase_render.png differ diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index 8248f7ad8..6f9cf6ef7 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -7,7 +7,7 @@ io.github.demchaav graph-compose-build - 1.8.0 + 1.9.0 ../aggregator/pom.xml @@ -25,9 +25,9 @@ 17 1.37 - 6.1.0 + 6.1.1 3.27.7 - 1.5.34 + 1.5.37 1.0.10 9.6.0 diff --git a/bundle/pom.xml b/bundle/pom.xml index c33c291e0..6ddb9496e 100644 --- a/bundle/pom.xml +++ b/bundle/pom.xml @@ -25,7 +25,7 @@ line. The graph-compose dependency below uses ${project.version}, so it follows automatically. --> - 1.8.0 + 1.9.0 pom GraphCompose Bundle diff --git a/docs/README.md b/docs/README.md index 44c722e6c..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. @@ -54,6 +59,8 @@ back here. ### Roadmaps & migrations - **[roadmaps/v1.6-roadmap.md](roadmaps/v1.6-roadmap.md)** — current development roadmap. +- **[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. 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/roadmaps/migration-v1-8-to-v1-9.md b/docs/roadmaps/migration-v1-8-to-v1-9.md new file mode 100644 index 000000000..d12a8a3d1 --- /dev/null +++ b/docs/roadmaps/migration-v1-8-to-v1-9.md @@ -0,0 +1,84 @@ +# Migration: v1.8 → v1.9 + +v1.9 — codenamed **"navigable"** — is **additive only**. Every public +type, method, and behaviour from v1.8 is unchanged: bump the dependency +to `1.9.0` and rebuild with **no source changes required**. Adding public +API is what turns the open cycle into a minor release. + +The theme of the release is **in-document navigation** — a rendered PDF +stops being a flat sequence of pages and becomes a document you can move +through: named anchors, internal links, a native clickable table of +contents, "see page N" cross-references, and a bookmark outline, all +emitted as native PDF `GoTo` actions and resolved in a single authoring +pass. + +If your application targets v1.8 today, there is nothing to do but +upgrade. The rest of this guide is a tour of what you can now reach for. + +## TL;DR + +Everything below is `@since 1.9.0` and purely additive — no v1.8 API is +replaced or removed. + +| Area | v1.9 addition | Reach for it when | +| --- | --- | --- | +| **In-PDF navigation** | `anchor(...)` targets + internal `link(...)`; sealed `DocumentLinkTarget` (`InternalLinkTarget` / `ExternalLinkTarget`) | clickable cross-references and `[text](#heading)`-style jumps emitted as native PDF `GoTo` actions | +| **Table of contents** | `addTableOfContents(toc -> toc.entry(label, anchor))` + `TocBuilder` / `DocumentLeader` | a native clickable TOC with dot leaders and auto-resolved page numbers | +| **Page references** | `addPageReference(anchor)` + `PageReferenceNode`; `DocumentSession.pageIndex()` → `PageIndex` / `PageReference` | a "see page N" cross-reference, or resolving any anchor to its page from code | +| **Bookmarks & viewer prefs** | container `bookmark(DocumentBookmarkOptions)`; `chrome().viewerPreferences(...)` + `DocumentViewerPreferences` / `DocumentPageMode` / `DocumentPageLayout` | a PDF outline (bookmark panel), and controlling how a reader opens the document | +| **Multi-section documents** | `GraphCompose.documents()` + `MultiSectionDocumentBuilder` / `MultiSectionDocument` | concatenating independently authored sections (own page size / margins / numbering) into one PDF, with links resolving across sections | +| **Per-page margins & bleed** | `pageMargins(List)`; `bleed(...)` + `DocumentBleed` / `DocumentEdge` | mixing a full-bleed cover with book-margin body pages, or running content to the trimmed page edge | +| **Row layout** | `row.columns(auto() / weight() / fixed())` + `DocumentRowColumn`; `flexSpacer()` / `pushRight()` / `arrangement(...)` + `RowArrangement`; `verticalAlign(...)` + `RowVerticalAlign` | column tracks, main-axis justify (push a badge flush right), and cross-axis seating within a row | +| **Inline chips / SVG / emoji** | inline highlight chips, inline `SvgIcon` runs, `RichText.emoji(":star:", size)` via the new `graph-compose-emoji` module | a code/badge chip, an icon on the text baseline, or colour emoji by shortcode | +| **Page numbering** | `DocumentPageNumbering` / `DocumentPageNumberStyle` | page-number offset / restart / numeral style in headers and footers | +| **Lines** | `LineBuilder.fill()`; `LineBuilder.lineCap(DocumentLineCap)` | a line that stretches to its slot (dot leaders), and round / square caps or dotted strokes | +| **Render to images** | render a `DocumentSession` straight to `BufferedImage` | a raster preview / thumbnail with no PDF round-trip | + +Runnable code for each lives in the +[examples gallery](../../examples/README.md); the exact public-API list +is in [`CHANGELOG.md`](../../CHANGELOG.md) under **v1.9.0**. + +## One behaviour to know about + +- **A negative page margin is now rejected; a negative bottom *content* + margin is honoured.** A negative `margin(...)` on the page fails fast at + construction instead of silently mis-laying-out the page, while an + intentional negative bottom margin on a block now pulls the following + content up as written. This only affects code that passed a negative + *page* margin — pass a non-negative one. + +## Things that did NOT break + +- Every entry point on `GraphCompose`, the full `DocumentSession` + authoring lifecycle (`compose`, `pageFlow`, add, `buildPdf`, export, + close, `layoutGraph`, `layoutSnapshot`). +- `DocumentDsl`, `BusinessTheme`, `DocumentPalette`, and the invoice / + proposal / CV / cover-letter / weekly-schedule template entry points + (V1 and V2). +- The fixed-layout and semantic backend SPIs and every public render + handler. +- Layout snapshots and visual-regression baselines — navigation markers + are non-visual, so a document that adds no anchors, links, or bookmarks + renders byte-for-byte as before. + +> **Deprecations (informational, not breaking).** +> `templates.api.CoverLetterTemplate` and the `cv.v2.components` +> `HeadlineRenderer` / `ContactRenderer` / `BannerRenderer` shims are now +> `@Deprecated(forRemoval = true, since = "1.9.0")` — superseded by the +> generic `DocumentTemplate` seam and the `cv.v2.widgets` widgets +> respectively. They still compile and behave exactly as before in 1.x; +> they are slated for removal in 2.0. See +> [`docs/api-stability.md`](../api-stability.md) §3 for the ledger. + +## Upgrading + +```xml + + io.github.demchaav + graph-compose + 1.9.0 + +``` + +That is the entire migration. Pull in any of the primitives above as you +need them. 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. diff --git a/examples/README.md b/examples/README.md index 0a51f6c65..e87e62dbf 100644 --- a/examples/README.md +++ b/examples/README.md @@ -69,6 +69,9 @@ are with the canonical DSL, then jump to its detailed section below. |---|---|---| | [Rich text](#rich-text) | Every `RichText` method (bold / italic / underline / link / colour / accent / size / append) | [PDF](../assets/readme/examples/rich-text-showcase.pdf) · [Source](src/main/java/com/demcha/examples/features/text/RichTextShowcaseExample.java) | | [Inline shapes](#inline-shapes) | `InlineShapeRun` — dots, arrows, chevrons, diamonds, stars, checkmarks and checkboxes drawn as geometry on the text baseline | [PDF](../assets/readme/examples/inline-shapes.pdf) · [Source](src/main/java/com/demcha/examples/features/text/InlineShapesExample.java) | +| [Inline highlight chips](#inline-highlight-chips) | `RichText.code(text)` / `chip(text, fg, bg)` / `highlight(text, style, bg, radius, padding)` — text on a rounded padded fill (inline code + status badges), wrapping across lines | [PDF](../assets/readme/examples/inline-highlight-chips.pdf) · [Source](src/main/java/com/demcha/examples/features/text/InlineHighlightExample.java) | +| [Inline SVG icons](#inline-svg-icons) | `RichText.svgIcon(icon, size)` — a parsed multi-colour `SvgIcon` on the text baseline, crisp at any zoom and carrying its own colours | [PDF](../assets/readme/examples/inline-svg-icons.pdf) · [Source](src/main/java/com/demcha/examples/features/text/InlineSvgIconExample.java) | +| [Colour emoji](#colour-emoji) | `RichText.emoji(":star:", size)` — GitHub-style shortcodes resolve to inline vector glyphs via the `graph-compose-emoji` artifact; unknown codes fall back to literal text | [PDF](../assets/readme/examples/emoji-shortcodes.pdf) · [Source](src/main/java/com/demcha/examples/features/text/EmojiShortcodeExample.java) | | [Section presets](#section-presets) | `pageBackground`, `band`, `softPanel`, `accentLeft / Right / Top / Bottom`, per-corner `DocumentCornerRadius` | [PDF](../assets/readme/examples/section-presets.pdf) · [Source](src/main/java/com/demcha/examples/features/text/SectionPresetsExample.java) | | [Nested lists](#nested-lists-v16) | `ListBuilder.addItem(label, Consumer)` — depth cascade, per-depth markers, mixed flat / nested authoring | [PDF](../assets/readme/examples/nested-list-showcase.pdf) · [Source](src/main/java/com/demcha/examples/features/lists/NestedListExample.java) | | [Composed table cells](#composed-table-cells-v16) | `DocumentTableCell.node(DocumentNode)` — paragraphs, lists, sub-tables inside cells with two-pass measurement | [PDF](../assets/readme/examples/composed-table-cell-showcase.pdf) · [Source](src/main/java/com/demcha/examples/features/tables/ComposedTableCellExample.java) | @@ -105,6 +108,7 @@ are with the canonical DSL, then jump to its detailed section below. | [PDF chrome](#pdf-chrome) | `DocumentMetadata`, `DocumentWatermark`, `DocumentHeaderFooter`, `DocumentBookmarkOptions` | [PDF](../assets/readme/examples/pdf-chrome.pdf) · [Source](src/main/java/com/demcha/examples/features/chrome/PdfChromeExample.java) | | [Page numbering](#page-numbering) | `DocumentPageNumbering` — offset / restart / roman / suppress-on-first-page for `{page}` / `{pages}` footer tokens | [PDF](../assets/readme/examples/page-numbering.pdf) · [Source](src/main/java/com/demcha/examples/features/chrome/PageNumberingExample.java) | | [Viewer preferences](#viewer-preferences) | `chrome().viewerPreferences(...)` — open with the bookmark panel (`USE_OUTLINES`), set page layout, or show the doc title in the window | [PDF](../assets/readme/examples/viewer-preferences.pdf) · [Source](src/main/java/com/demcha/examples/features/chrome/ViewerPreferencesExample.java) | +| [In-PDF navigation](#in-pdf-navigation) | `anchor(...)` destinations + internal `linkTo(...)` / `inlineLinkTo(...)` / `shapeLinkTo(...)` — clickable cross-references and footnotes as native PDF GoTo actions; forward references resolve in a deferred pass | [PDF](../assets/readme/examples/in-pdf-navigation.pdf) · [Source](src/main/java/com/demcha/examples/features/navigation/InPdfNavigationExample.java) | | [Page references](#page-references) | `addPageReference(anchor)` — print the page an `anchor(...)` lands on (a native "see page N" cross-reference), resolved in one authoring pass | [PDF](../assets/readme/examples/page-reference.pdf) · [Source](src/main/java/com/demcha/examples/features/navigation/PageReferenceExample.java) | | [Table of contents](#table-of-contents) | `addTableOfContents(toc -> toc.entry(label, anchor))` — a native clickable TOC with dot leaders and auto-resolved page numbers | [PDF](../assets/readme/examples/table-of-contents.pdf) · [Source](src/main/java/com/demcha/examples/features/navigation/TocExample.java) | | [Container bookmarks](#container-bookmarks) | `section.bookmark(new DocumentBookmarkOptions(title))` — make a section / container a PDF outline (bookmark-panel) target | [PDF](../assets/readme/examples/container-bookmark.pdf) · [Source](src/main/java/com/demcha/examples/features/navigation/ContainerBookmarkExample.java) | @@ -642,6 +646,62 @@ via `CheckmarkStyle` / `ArrowStyle`. [📄 View PDF](../assets/readme/examples/inline-shapes.pdf) · [📜 Full source](src/main/java/com/demcha/examples/features/text/InlineShapesExample.java) +### Inline highlight chips + +`RichText.code(text)` / `chip(text, fg, bg)` / `highlight(text, style, bg, +radius, padding)` (`@since 1.9.0`) draw text on a rounded, padded background +fill on the baseline — the GitHub inline `code` look and inline status badges. +`code` ships engine defaults (monospace + a light chip), `chip` colours a +badge, and `highlight` is the full primitive. A multi-word highlight wraps +across lines, painting one continuous rounded fill per visual fragment. On +`ParagraphBuilder` the calls are `inlineCode` / `inlineChip` / `inlineHighlight`. + +```java +.addRich(rich -> rich + .plain("Run ").code("./mvnw verify").plain(" — status ") + .chip(" Paid ", paidFg, paidBg) + .highlight("rounded", style, fill, 8.0, DocumentInsets.symmetric(2, 8))) +``` + +[📄 View PDF](../assets/readme/examples/inline-highlight-chips.pdf) · +[📜 Full source](src/main/java/com/demcha/examples/features/text/InlineHighlightExample.java) + +### Inline SVG icons + +`RichText.svgIcon(icon, size)` / `ParagraphBuilder.inlineSvgIcon(...)` +(`@since 1.9.0`) place a parsed `SvgIcon` on the text baseline, so multi-colour +vector glyphs flow inside a line of text — crisp at any zoom, carrying their own +colours, with no dependence on the active font's glyph coverage. `size` is the +glyph height in points; width follows the icon's aspect ratio. This is the +engine path behind vector colour emoji. + +```java +.addRich(rich -> rich + .svgIcon(check, 10).plain(" Deploy succeeded ") + .svgIcon(warn, 10).plain(" Disk almost full")) +``` + +[📄 View PDF](../assets/readme/examples/inline-svg-icons.pdf) · +[📜 Full source](src/main/java/com/demcha/examples/features/text/InlineSvgIconExample.java) + +### Colour emoji + +`RichText.emoji(":star:", size)` / `ParagraphBuilder.inlineEmoji(...)` +(`@since 1.9.0`) resolve a GitHub-style shortcode to an inline vector colour +glyph on the baseline — crisp at any zoom, no emoji font required. Glyphs come +from the `graph-compose-emoji` companion artifact on the classpath. Resolution +is lenient: an unknown shortcode falls back to its literal text, exactly the way +GitHub renders an unrecognised `:code:`. + +```java +.addRich(rich -> rich + .plain("Ship it ").emoji(":rocket:", 11).plain(" ") + .emoji(":white_check_mark:", 11).plain(" ").emoji(":tada:", 11)) +``` + +[📄 View PDF](../assets/readme/examples/emoji-shortcodes.pdf) · +[📜 Full source](src/main/java/com/demcha/examples/features/text/EmojiShortcodeExample.java) + ### Section presets `pageBackground`, `band`, `softPanel`, the four @@ -780,6 +840,25 @@ document.chrome().viewerPreferences(DocumentViewerPreferences.builder() [📄 View PDF](../assets/readme/examples/viewer-preferences.pdf) · [📜 Full source](src/main/java/com/demcha/examples/features/chrome/ViewerPreferencesExample.java) +### In-PDF navigation + +`anchor(name)` marks a destination on a section, paragraph, or inline run; +`linkTo(label, style, anchor)` / `inlineLinkTo(text, anchor)` / +`shapeLinkTo(shape, color, anchor)` (`@since 1.9.0`) jump to it as native PDF +`GoTo` actions — a clickable table of contents, `[text](#heading)`-style links, +and bidirectional footnotes. Anchors resolve in a deferred pass, so a link may +target an anchor that appears later in the document (a forward reference). +External `link(label, new DocumentLinkOptions(url))` is unchanged. + +```java +.addRich(RichText.text("See the ").linkTo("overview", linkStyle, "overview")) +// …further down… +.addSection("Overview", s -> s.anchor("overview") /* … */) +``` + +[📄 View PDF](../assets/readme/examples/in-pdf-navigation.pdf) · +[📜 Full source](src/main/java/com/demcha/examples/features/navigation/InPdfNavigationExample.java) + ### Page references `addPageReference(anchor)` prints the page a declared `anchor(...)` lands on — a diff --git a/examples/pom.xml b/examples/pom.xml index 9efb1d2d4..cbff15b74 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -7,7 +7,7 @@ io.github.demchaav graph-compose-build - 1.8.0 + 1.9.0 ../aggregator/pom.xml @@ -18,10 +18,10 @@ ${project.version} - 1.5.34 + 1.5.37 17 - 6.1.0 + 6.1.1 3.27.7 @@ -98,6 +98,28 @@ + + + + src/main/resources + true + + banner.properties + + + + src/main/resources + false + + banner.properties + + + org.apache.maven.plugins diff --git a/examples/src/main/java/com/demcha/examples/flagships/EngineDeckExample.java b/examples/src/main/java/com/demcha/examples/flagships/EngineDeckExample.java index 48346b2a5..c73a92547 100644 --- a/examples/src/main/java/com/demcha/examples/flagships/EngineDeckExample.java +++ b/examples/src/main/java/com/demcha/examples/flagships/EngineDeckExample.java @@ -48,11 +48,14 @@ import com.demcha.compose.font.FontName; import com.demcha.examples.support.ExampleOutputPaths; +import java.awt.image.BufferedImage; +import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.List; import java.util.Objects; +import java.util.Properties; /** * Flagship "what is GraphCompose" capability deck — a multi-page landscape @@ -100,8 +103,35 @@ */ public final class EngineDeckExample { - private static final String VERSION = "1.8.0"; - private static final String CODENAME = "illustrative"; + /** + * Release version + codename shown on the banner. Sourced from the filtered + * {@code banner.properties} ({@code version} = Maven {@code @project.version@}, + * {@code codename} = the per-minor release name), so the hero is always + * current with the release the build was cut from rather than a hand-bumped + * literal. Falls back to {@code "dev"} when run without Maven resource + * filtering (e.g. straight from an IDE), so the banner never prints the raw + * {@code @…@} token. + */ + private static final String VERSION; + private static final String CODENAME; + + static { + Properties banner = new Properties(); + try (InputStream in = EngineDeckExample.class.getResourceAsStream("/banner.properties")) { + if (in != null) { + banner.load(in); + } + } catch (IOException ignored) { + // Missing/unreadable metadata falls through to the defaults below. + } + VERSION = resolved(banner.getProperty("version"), "dev"); + CODENAME = resolved(banner.getProperty("codename"), ""); + } + + /** Returns {@code value} unless it is blank or an unfiltered {@code @…@} token. */ + private static String resolved(String value, String fallback) { + return value == null || value.isBlank() || value.startsWith("@") ? fallback : value.trim(); + } private EngineDeckExample() { } @@ -125,22 +155,24 @@ public static Path generate() throws Exception { } /** - * Renders page 1's banner as a standalone, full-bleed hero. The dark - * violet field is painted as the canonical {@code pageBackground}, so it - * fills the whole landscape page — margins and corners included — and the - * rasterised image carries no white frame, only the banner itself. This is - * the source of the repository README hero - * ({@code assets/readme/repository_showcase_render.png}, produced by - * {@link com.demcha.examples.support.PdfPageRasterizer}); re-render it after a - * version bump — the banner reads {@link #VERSION} / {@link #CODENAME}, so - * the hero stays current with one rebuild. + * Renders the standalone hero banner to a raster image straight from the + * engine via {@link DocumentSession#toImage(int, int)} ({@code @since 1.9.0}) + * — no intermediate PDF and no external rasterizer. This is the source of the + * repository README hero ({@code assets/readme/repository_showcase_render.png}, + * written by {@link com.demcha.examples.support.ReadmeBannerRenderer}). The + * dark violet field is the canonical {@code pageBackground} on a page cropped + * to wrap the content, so the image is all banner and no white frame. The + * version pill reads {@link #VERSION} / {@link #CODENAME} from the filtered + * {@code banner.properties}, so the hero stays current with the release the + * build was cut from; {@code cut-release.ps1} re-renders it on every tag. * - * @return the generated single-page banner PDF path + * @param dpi raster resolution in dots per inch; 200 matches the committed asset + * @return the rendered banner image * @throws Exception when rendering or icon IO fails + * @since 1.9.0 */ - public static Path generateBanner() throws Exception { - Path outputFile = ExampleOutputPaths.prepare("flagships", "engine-banner.pdf"); - try (DocumentSession document = GraphCompose.document(outputFile) + public static BufferedImage renderBannerImage(int dpi) throws Exception { + try (DocumentSession document = GraphCompose.document() // The banner content is a top-anchored stack; the default // A4-landscape page left a thick dead dark border around it. // Crop the page to wrap the content tightly — width fits the @@ -150,21 +182,46 @@ public static Path generateBanner() throws Exception { .pageBackground(HERO_BG) .margin(8, 8, 8, 8) .create()) { - document.metadata(DocumentMetadata.builder() - .title("GraphCompose v" + VERSION + " — " + CODENAME) - .author("GraphCompose") - .subject("GraphCompose banner — the engine's own brand hero") - .producer("GraphCompose (PDFBox 3.0)") - .build()); - document.pageFlow() - .name("EngineBanner") - .addSection("Banner", EngineDeckExample::banner) - .build(); + composeBannerInto(document); + return document.toImage(0, dpi); + } + } + + /** + * Renders the same standalone banner to a PDF — kept for a vector/print copy + * of the hero and as the layout the snapshot test guards. The committed + * README image comes from {@link #renderBannerImage(int)}, not this file. + * + * @return the generated single-page banner PDF path + * @throws Exception when rendering or icon IO fails + */ + public static Path generateBanner() throws Exception { + Path outputFile = ExampleOutputPaths.prepare("flagships", "engine-banner.pdf"); + try (DocumentSession document = GraphCompose.document(outputFile) + .pageSize(801, 525) + .pageBackground(HERO_BG) + .margin(8, 8, 8, 8) + .create()) { + composeBannerInto(document); document.buildPdf(); } return outputFile; } + /** Banner metadata + the single banner section — shared by the image and PDF renders. */ + private static void composeBannerInto(DocumentSession document) { + document.metadata(DocumentMetadata.builder() + .title("GraphCompose v" + VERSION + " — " + CODENAME) + .author("GraphCompose") + .subject("GraphCompose banner — the engine's own brand hero") + .producer("GraphCompose (PDFBox 3.0)") + .build()); + document.pageFlow() + .name("EngineBanner") + .addSection("Banner", EngineDeckExample::banner) + .build(); + } + /** * Composes the four deck pages onto a session — shared by {@link #generate()} * and the layout snapshot test, so the test guards the very layout we ship. diff --git a/examples/src/main/java/com/demcha/examples/support/ReadmeBannerRenderer.java b/examples/src/main/java/com/demcha/examples/support/ReadmeBannerRenderer.java new file mode 100644 index 000000000..f95d11c53 --- /dev/null +++ b/examples/src/main/java/com/demcha/examples/support/ReadmeBannerRenderer.java @@ -0,0 +1,75 @@ +package com.demcha.examples.support; + +import com.demcha.examples.flagships.EngineDeckExample; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * Renders the README hero banner straight to its committed PNG via the engine's + * {@code DocumentSession.toImage(...)} path ({@code @since 1.9.0}) — the + * replacement for the old render-to-PDF-then-{@link PdfPageRasterizer} + * round-trip. The banner's version pill is sourced from the filtered + * {@code banner.properties}, so running this after a version bump stamps the + * hero with the current release. {@code cut-release.ps1} invokes it during the + * release commit so {@code assets/readme/repository_showcase_render.png} ships + * in lockstep with every tag, instead of drifting until someone re-renders by + * hand. + * + *

Usage — pass an explicit output path (exec:java runs with the examples + * module as its working directory, so a relative default would land in the + * wrong place); DPI defaults to 200, matching the committed asset:

+ *
+ * ./mvnw -B -ntp -f examples/pom.xml -DskipTests exec:java \
+ *   -Dexec.mainClass=com.demcha.examples.support.ReadmeBannerRenderer \
+ *   -Dexec.args="<outputPng> [dpi=200]"
+ * 
+ * + * @author Artem Demchyshyn + * @since 1.9.0 + */ +public final class ReadmeBannerRenderer { + + /** Resolution of the committed hero asset; high enough for a crisp README image. */ + private static final int DEFAULT_DPI = 200; + + private ReadmeBannerRenderer() { + } + + public static void main(String[] args) throws Exception { + if (args.length < 1 || args[0].isBlank()) { + System.err.println("Usage: ReadmeBannerRenderer [dpi=200]"); + System.exit(2); + } + Path outputPng = Paths.get(args[0]).toAbsolutePath().normalize(); + int dpi = args.length >= 2 ? Integer.parseInt(args[1]) : DEFAULT_DPI; + + Path written = render(outputPng, dpi); + System.out.println("Rendered README banner -> " + written + " (" + dpi + " DPI)"); + } + + /** + * Renders the hero banner and writes it as a PNG, creating parent + * directories as needed. + * + * @param outputPng destination file + * @param dpi raster resolution in dots per inch + * @return the written path + * @throws Exception if rendering fails + * @throws IllegalStateException if the PNG encoder rejects the image + */ + public static Path render(Path outputPng, int dpi) throws Exception { + BufferedImage image = EngineDeckExample.renderBannerImage(dpi); + Path parent = outputPng.toAbsolutePath().getParent(); + if (parent != null) { + Files.createDirectories(parent); + } + if (!ImageIO.write(image, "png", outputPng.toFile())) { + throw new IllegalStateException("No PNG ImageIO writer accepted the banner: " + outputPng); + } + return outputPng; + } +} diff --git a/examples/src/main/resources/banner.properties b/examples/src/main/resources/banner.properties new file mode 100644 index 000000000..d3161d75e --- /dev/null +++ b/examples/src/main/resources/banner.properties @@ -0,0 +1,10 @@ +# README hero banner metadata. +# +# `version` is filled by Maven resource filtering from the reactor +# ${project.version} at build time (see examples/pom.xml ), so the +# rendered hero is never a hand-bumped literal and can never drift from the +# release it was cut from. `codename` is the per-minor release name, set during +# release prep. cut-release.ps1 re-renders the banner on every tag, stamping the +# committed assets/readme/repository_showcase_render.png with both values. +version=@project.version@ +codename=navigable diff --git a/examples/src/test/java/com/demcha/examples/support/ReadmeBannerRendererTest.java b/examples/src/test/java/com/demcha/examples/support/ReadmeBannerRendererTest.java new file mode 100644 index 000000000..3fd64800e --- /dev/null +++ b/examples/src/test/java/com/demcha/examples/support/ReadmeBannerRendererTest.java @@ -0,0 +1,59 @@ +package com.demcha.examples.support; + +import com.demcha.examples.flagships.EngineDeckExample; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.awt.image.BufferedImage; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Properties; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Verifies the README hero renders straight to an image through the engine's + * {@code toImage} path — no intermediate PDF, no external rasterizer — and that + * the writer lands a non-trivial PNG. + */ +class ReadmeBannerRendererTest { + + @Test + void rendersANonEmptyBannerImageWithoutAPdfRoundTrip() throws Exception { + BufferedImage image = EngineDeckExample.renderBannerImage(96); + + // The banner page is 801x525pt; at 96 DPI that is ~1068x700px. Assert + // generous lower bounds so the test guards "rendered something real" + // without pinning exact pixels (DPI/metric drift would make that brittle). + assertThat(image.getWidth()).isGreaterThan(600); + assertThat(image.getHeight()).isGreaterThan(300); + } + + @Test + void writesThePngToTheGivenPath(@TempDir Path tmp) throws Exception { + Path png = tmp.resolve("nested").resolve("banner.png"); + + Path written = ReadmeBannerRenderer.render(png, 72); + + assertThat(written).isEqualTo(png); + assertThat(png).exists(); + assertThat(Files.size(png)).isGreaterThan(2_000L); + } + + @Test + void bannerPropertiesVersionIsFilledByMavenResourceFiltering() throws Exception { + // The on-classpath copy is the build's filtered output: if examples/pom.xml + // ever loses the filtering, version stays the raw @project.version@ token + // and the hero silently stops carrying the real release version. + Properties props = new Properties(); + try (InputStream in = getClass().getResourceAsStream("/banner.properties")) { + assertThat(in).describedAs("banner.properties must be on the classpath").isNotNull(); + props.load(in); + } + assertThat(props.getProperty("version")) + .describedAs("banner version must be filtered to the project version, not the raw @project.version@ token") + .doesNotContain("@") + .matches("\\d.*"); + } +} diff --git a/pom.xml b/pom.xml index b24ed21d2..75661f348 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ io.github.demchaav graph-compose - 1.8.0 + 1.9.0 GraphCompose A declarative layout engine for programmatic document generation, implemented primarily in Java. @@ -48,7 +48,7 @@ 0.64.8 2.21.4 - 1.5.34 + 1.5.37 1.18.46 3.0.7 5.5.1 @@ -58,7 +58,7 @@ 3.27.7 - 6.1.0 + 6.1.1 5.23.0 1.18.10 diff --git a/scripts/cut-release.ps1 b/scripts/cut-release.ps1 index c08f22e8c..0e4d7f502 100644 --- a/scripts/cut-release.ps1 +++ b/scripts/cut-release.ps1 @@ -340,6 +340,39 @@ function Run-ShowcaseSync { } } +function Render-ReadmeBanner { + # Re-renders assets/readme/repository_showcase_render.png straight from the + # engine (DocumentSession.toImage, @since 1.9.0) so the hero's version pill + # carries the just-bumped ${project.version} (read from the filtered + # banner.properties). The `compile` is REQUIRED: banner.properties is filtered + # at examples-compile time, so the examples module must be recompiled AFTER the + # Step-1 version bump — otherwise the banner would carry the previous release + # version. Runs after Run-ShowcaseSync, which already installed the bumped root + # artifact into the local m2 cache so the examples module resolves it. + Write-Host " > Re-render the version-stamped README hero banner" -ForegroundColor Cyan + $banner = Join-Path $repoRoot 'assets/readme/repository_showcase_render.png' + $execProp = '"-Dexec.mainClass=com.demcha.examples.support.ReadmeBannerRenderer"' + $execArgs = "`"-Dexec.args=$banner`"" + if ($DryRun) { + Write-Host " [DRY RUN] $mvnw -f examples/pom.xml -DskipTests compile exec:java $execProp $execArgs" -ForegroundColor Yellow + return + } + Push-Location $repoRoot + try { + & $mvnw -B -ntp -f examples/pom.xml -DskipTests compile exec:java $execProp $execArgs 2>&1 | ForEach-Object { + if ($_ -match 'Rendered README banner|BUILD SUCCESS|BUILD FAILURE|ERROR') { + Write-Host " $_" -ForegroundColor DarkGray + } + } + if ($LASTEXITCODE -ne 0) { + throw "README banner render failed (exit $LASTEXITCODE)" + } + } finally { + Pop-Location + } + Note "banner: assets/readme/repository_showcase_render.png re-rendered" +} + # ============================================================ # Mode: -PostReleaseOnly # ============================================================ @@ -483,8 +516,9 @@ try { Step 4 "Regenerate web/examples.json with $tag links" Run-ShowcaseSync + Render-ReadmeBanner } else { - Step 3 "Skipped showcase GH_BASE flip + regen (-SkipShowcase)" + Step 3 "Skipped showcase GH_BASE flip + regen + banner (-SkipShowcase)" } if (-not $SkipVerify) { @@ -523,7 +557,8 @@ try { $commitFiles += @( 'examples/src/main/java/com/demcha/examples/support/ShowcaseMetadata.java', 'web/examples.json', - 'web/showcase' + 'web/showcase', + 'assets/readme/repository_showcase_render.png' ) } if ($DryRun) { diff --git a/src/main/java/com/demcha/compose/document/api/DocumentSession.java b/src/main/java/com/demcha/compose/document/api/DocumentSession.java index 5f0586f73..8ae52b00f 100644 --- a/src/main/java/com/demcha/compose/document/api/DocumentSession.java +++ b/src/main/java/com/demcha/compose/document/api/DocumentSession.java @@ -475,6 +475,7 @@ public DocumentSession pageBackgrounds(List fills) { * @param rules ordered list of per-page margin overrides, or {@code null}/empty to clear * @return this session * @throws IllegalStateException if this session has already been closed + * @since 1.9.0 */ public DocumentSession pageMargins(List rules) { ensureOpen(); diff --git a/src/main/java/com/demcha/compose/document/templates/cv/v2/components/BannerRenderer.java b/src/main/java/com/demcha/compose/document/templates/cv/v2/components/BannerRenderer.java index 4a2e6204a..0d1c4beca 100644 --- a/src/main/java/com/demcha/compose/document/templates/cv/v2/components/BannerRenderer.java +++ b/src/main/java/com/demcha/compose/document/templates/cv/v2/components/BannerRenderer.java @@ -5,7 +5,7 @@ import com.demcha.compose.document.templates.cv.v2.widgets.SectionHeader; /** - * @deprecated Use + * @deprecated since 1.9.0; removed in 2.0. Use * {@link com.demcha.compose.document.templates.cv.v2.widgets.SectionHeader#banner} * instead — the widget groups the banner alongside its sibling * variants ({@code underlined}, {@code flat}) so picking a section- @@ -13,7 +13,7 @@ * delegating shim so v2 code written before the widgets layer keeps * compiling unchanged. */ -@Deprecated(forRemoval = true) +@Deprecated(since = "1.9.0", forRemoval = true) public final class BannerRenderer { private BannerRenderer() { diff --git a/src/main/java/com/demcha/compose/document/templates/cv/v2/components/ContactRenderer.java b/src/main/java/com/demcha/compose/document/templates/cv/v2/components/ContactRenderer.java index 88ec45955..4e74b7a3f 100644 --- a/src/main/java/com/demcha/compose/document/templates/cv/v2/components/ContactRenderer.java +++ b/src/main/java/com/demcha/compose/document/templates/cv/v2/components/ContactRenderer.java @@ -6,14 +6,14 @@ import com.demcha.compose.document.templates.core.identity.ContactLine; /** - * @deprecated Use + * @deprecated since 1.9.0; removed in 2.0. Use * {@link com.demcha.compose.document.templates.core.identity.ContactLine#centered} * instead — the widget gives you named centred/right-aligned * variants plus a configurable field order. Kept as a thin * delegating shim so v2 code written before the widgets layer keeps * compiling unchanged. */ -@Deprecated(forRemoval = true) +@Deprecated(since = "1.9.0", forRemoval = true) public final class ContactRenderer { private ContactRenderer() { diff --git a/src/main/java/com/demcha/compose/document/templates/cv/v2/components/HeadlineRenderer.java b/src/main/java/com/demcha/compose/document/templates/cv/v2/components/HeadlineRenderer.java index c724c8b56..a25dc85a6 100644 --- a/src/main/java/com/demcha/compose/document/templates/cv/v2/components/HeadlineRenderer.java +++ b/src/main/java/com/demcha/compose/document/templates/cv/v2/components/HeadlineRenderer.java @@ -6,14 +6,14 @@ import com.demcha.compose.document.templates.core.identity.Headline; /** - * @deprecated Use + * @deprecated since 1.9.0; removed in 2.0. Use * {@link com.demcha.compose.document.templates.core.identity.Headline#spacedCentered} * instead — the widget gives you a named API plus alignment + * spaced-caps variants, while this class only ever did the * centred-spaced-caps form. Kept as a thin delegating shim so v2 * code written before the widgets layer keeps compiling unchanged. */ -@Deprecated(forRemoval = true) +@Deprecated(since = "1.9.0", forRemoval = true) public final class HeadlineRenderer { private HeadlineRenderer() { diff --git a/src/test/java/com/demcha/documentation/PublicApiNoEngineLeakTest.java b/src/test/java/com/demcha/documentation/PublicApiNoEngineLeakTest.java index 946c17468..0f20abbe2 100644 --- a/src/test/java/com/demcha/documentation/PublicApiNoEngineLeakTest.java +++ b/src/test/java/com/demcha/documentation/PublicApiNoEngineLeakTest.java @@ -41,6 +41,11 @@ class PublicApiNoEngineLeakTest { PROJECT_ROOT.resolve("src/main/java/com/demcha/compose/document/style"), PROJECT_ROOT.resolve("src/main/java/com/demcha/compose/document/table"), PROJECT_ROOT.resolve("src/main/java/com/demcha/compose/document/image"), + PROJECT_ROOT.resolve("src/main/java/com/demcha/compose/document/output"), + PROJECT_ROOT.resolve("src/main/java/com/demcha/compose/document/snapshot"), + PROJECT_ROOT.resolve("src/main/java/com/demcha/compose/document/theme"), + PROJECT_ROOT.resolve("src/main/java/com/demcha/compose/document/exceptions"), + PROJECT_ROOT.resolve("src/main/java/com/demcha/compose/document/emoji"), PROJECT_ROOT.resolve("src/main/java/com/demcha/compose/font")); /** diff --git a/src/test/java/com/demcha/documentation/VersionConsistencyGuardTest.java b/src/test/java/com/demcha/documentation/VersionConsistencyGuardTest.java index 0d2648020..5a4fd50d1 100644 --- a/src/test/java/com/demcha/documentation/VersionConsistencyGuardTest.java +++ b/src/test/java/com/demcha/documentation/VersionConsistencyGuardTest.java @@ -106,6 +106,28 @@ void benchmarksDependencyDerivesVersionAndIsNotHardcoded() throws IOException { .isFalse(); } + /** + * The README hero banner reads its version pill from the filtered examples + * {@code banner.properties}; its {@code version} line must be the Maven + * {@code @project.version@} token so the rendered hero always carries the + * release version instead of a hand-bumped literal (the drift that left the + * banner reading v1.8.0 while the repo was on v1.9.0). {@code cut-release.ps1} + * re-renders the banner on release, and this fails the verify gate the moment + * the source is hardcoded again. + */ + @Test + void readmeBannerVersionDerivesFromProjectVersion() throws Exception { + String props = Files.readString( + PROJECT_ROOT.resolve("examples/src/main/resources/banner.properties")); + + assertThat(Pattern.compile("(?m)^\\s*version\\s*=\\s*@project\\.version@\\s*$").matcher(props).find()) + .describedAs("examples/src/main/resources/banner.properties must source the hero version from @project.version@, not a literal") + .isTrue(); + assertThat(Pattern.compile("(?m)^\\s*version\\s*=\\s*v?\\d").matcher(props).find()) + .describedAs("banner.properties must not hardcode a numeric version — that reintroduces the stale-hero drift") + .isFalse(); + } + @Test void readmeInstallSnippetsMatchTheProjectVersion() throws Exception { Set targets = acceptableTargets(); diff --git a/web/examples.json b/web/examples.json index 4b4b9aad8..9d581aeea 100644 --- a/web/examples.json +++ b/web/examples.json @@ -15,7 +15,7 @@ "tags": ["cv", "banner", "blue"], "pdf": "showcase/pdf/templates/cv/cv-blue-banner-v2.pdf", "screenshot": "showcase/screenshots/templates/cv/cv-blue-banner-v2.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/templates/cv/v2/CvBlueBannerExample.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/templates/cv/v2/CvBlueBannerExample.java" }, { "id": "cv-boxed-sections-v2", @@ -24,7 +24,7 @@ "tags": ["cv", "structured"], "pdf": "showcase/pdf/templates/cv/cv-boxed-sections-v2.pdf", "screenshot": "showcase/screenshots/templates/cv/cv-boxed-sections-v2.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/templates/cv/v2/CvBoxedV2Example.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/templates/cv/v2/CvBoxedV2Example.java" }, { "id": "cv-centered-headline-v2", @@ -33,7 +33,7 @@ "tags": ["cv", "centered"], "pdf": "showcase/pdf/templates/cv/cv-centered-headline-v2.pdf", "screenshot": "showcase/screenshots/templates/cv/cv-centered-headline-v2.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/templates/cv/v2/CvCenteredHeadlineExample.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/templates/cv/v2/CvCenteredHeadlineExample.java" }, { "id": "cv-classic-serif-v2", @@ -42,7 +42,7 @@ "tags": ["cv", "serif", "two-page"], "pdf": "showcase/pdf/templates/cv/cv-classic-serif-v2.pdf", "screenshot": "showcase/screenshots/templates/cv/cv-classic-serif-v2.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/templates/cv/v2/CvClassicSerifExample.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/templates/cv/v2/CvClassicSerifExample.java" }, { "id": "cv-compact-mono-v2", @@ -51,7 +51,7 @@ "tags": ["cv", "compact", "mono"], "pdf": "showcase/pdf/templates/cv/cv-compact-mono-v2.pdf", "screenshot": "showcase/screenshots/templates/cv/cv-compact-mono-v2.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/templates/cv/v2/CvCompactMonoExample.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/templates/cv/v2/CvCompactMonoExample.java" }, { "id": "cv-editorial-blue-v2", @@ -60,7 +60,7 @@ "tags": ["cv", "editorial", "blue"], "pdf": "showcase/pdf/templates/cv/cv-editorial-blue-v2.pdf", "screenshot": "showcase/screenshots/templates/cv/cv-editorial-blue-v2.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/templates/cv/v2/CvEditorialBlueExample.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/templates/cv/v2/CvEditorialBlueExample.java" }, { "id": "cv-engineering-resume-v2", @@ -69,7 +69,7 @@ "tags": ["cv", "tech"], "pdf": "showcase/pdf/templates/cv/cv-engineering-resume-v2.pdf", "screenshot": "showcase/screenshots/templates/cv/cv-engineering-resume-v2.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/templates/cv/v2/CvEngineeringResumeExample.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/templates/cv/v2/CvEngineeringResumeExample.java" }, { "id": "cv-executive-v2", @@ -78,7 +78,7 @@ "tags": ["cv", "executive"], "pdf": "showcase/pdf/templates/cv/cv-executive-v2.pdf", "screenshot": "showcase/screenshots/templates/cv/cv-executive-v2.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/templates/cv/v2/CvExecutiveExample.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/templates/cv/v2/CvExecutiveExample.java" }, { "id": "cv-minimal-underlined-v2", @@ -87,7 +87,7 @@ "tags": ["cv", "minimal"], "pdf": "showcase/pdf/templates/cv/cv-minimal-underlined-v2.pdf", "screenshot": "showcase/screenshots/templates/cv/cv-minimal-underlined-v2.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/templates/cv/v2/CvMinimalUnderlinedExample.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/templates/cv/v2/CvMinimalUnderlinedExample.java" }, { "id": "cv-mint-editorial-v2", @@ -96,7 +96,7 @@ "tags": ["cv", "editorial", "mint"], "pdf": "showcase/pdf/templates/cv/cv-mint-editorial-v2.pdf", "screenshot": "showcase/screenshots/templates/cv/cv-mint-editorial-v2.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/templates/cv/v2/CvMintEditorialExample.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/templates/cv/v2/CvMintEditorialExample.java" }, { "id": "cv-modern-professional-v2", @@ -105,7 +105,7 @@ "tags": ["cv", "minimal"], "pdf": "showcase/pdf/templates/cv/cv-modern-professional-v2.pdf", "screenshot": "showcase/screenshots/templates/cv/cv-modern-professional-v2.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/templates/cv/v2/CvModernV2Example.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/templates/cv/v2/CvModernV2Example.java" }, { "id": "cv-monogram-sidebar-v2", @@ -114,7 +114,7 @@ "tags": ["cv", "sidebar", "monogram"], "pdf": "showcase/pdf/templates/cv/cv-monogram-sidebar-v2.pdf", "screenshot": "showcase/screenshots/templates/cv/cv-monogram-sidebar-v2.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/templates/cv/v2/CvMonogramSidebarExample.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/templates/cv/v2/CvMonogramSidebarExample.java" }, { "id": "cv-nordic-clean-v2", @@ -123,7 +123,7 @@ "tags": ["cv", "sidebar"], "pdf": "showcase/pdf/templates/cv/cv-nordic-clean-v2.pdf", "screenshot": "showcase/screenshots/templates/cv/cv-nordic-clean-v2.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/templates/cv/v2/CvNordicCleanExample.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/templates/cv/v2/CvNordicCleanExample.java" }, { "id": "cv-panel-v2", @@ -132,7 +132,7 @@ "tags": ["cv", "panel"], "pdf": "showcase/pdf/templates/cv/cv-panel-v2.pdf", "screenshot": "showcase/screenshots/templates/cv/cv-panel-v2.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/templates/cv/v2/CvPanelExample.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/templates/cv/v2/CvPanelExample.java" }, { "id": "cv-sidebar-portrait-v2", @@ -141,7 +141,7 @@ "tags": ["cv", "sidebar", "portrait"], "pdf": "showcase/pdf/templates/cv/cv-sidebar-portrait-v2.pdf", "screenshot": "showcase/screenshots/templates/cv/cv-sidebar-portrait-v2.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/templates/cv/v2/CvSidebarPortraitExample.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/templates/cv/v2/CvSidebarPortraitExample.java" }, { "id": "cv-timeline-minimal-v2", @@ -150,7 +150,7 @@ "tags": ["cv", "timeline"], "pdf": "showcase/pdf/templates/cv/cv-timeline-minimal-v2.pdf", "screenshot": "showcase/screenshots/templates/cv/cv-timeline-minimal-v2.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/templates/cv/v2/CvTimelineMinimalExample.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/templates/cv/v2/CvTimelineMinimalExample.java" } ] }, @@ -165,7 +165,7 @@ "tags": ["proposal", "proposal", "cinematic"], "pdf": "showcase/pdf/templates/proposal/project-proposal-cinematic.pdf", "screenshot": "showcase/screenshots/templates/proposal/project-proposal-cinematic.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/templates/proposal/CinematicProposalFileExample.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/templates/proposal/CinematicProposalFileExample.java" }, { "id": "proposal", @@ -174,7 +174,7 @@ "tags": ["proposal", "proposal"], "pdf": "showcase/pdf/templates/proposal/proposal.pdf", "screenshot": "showcase/screenshots/templates/proposal/proposal.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/templates/proposal/ProposalFileExample.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/templates/proposal/ProposalFileExample.java" }, { "id": "proposal-cinematic", @@ -183,7 +183,7 @@ "tags": ["proposal", "proposal", "cinematic"], "pdf": "showcase/pdf/templates/proposal/proposal-cinematic.pdf", "screenshot": "showcase/screenshots/templates/proposal/proposal-cinematic.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/templates/proposal/ProposalCinematicFileExample.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/templates/proposal/ProposalCinematicFileExample.java" } ] }, @@ -198,7 +198,7 @@ "tags": ["invoice", "invoice"], "pdf": "showcase/pdf/templates/invoice/invoice.pdf", "screenshot": "showcase/screenshots/templates/invoice/invoice.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/templates/invoice/InvoiceFileExample.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/templates/invoice/InvoiceFileExample.java" }, { "id": "invoice-cinematic", @@ -207,7 +207,7 @@ "tags": ["invoice", "invoice", "cinematic"], "pdf": "showcase/pdf/templates/invoice/invoice-cinematic.pdf", "screenshot": "showcase/screenshots/templates/invoice/invoice-cinematic.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/templates/invoice/InvoiceCinematicFileExample.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/templates/invoice/InvoiceCinematicFileExample.java" } ] }, @@ -222,7 +222,7 @@ "tags": ["schedule", "schedule", "table"], "pdf": "showcase/pdf/templates/schedule/weekly-schedule.pdf", "screenshot": "showcase/screenshots/templates/schedule/weekly-schedule.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/templates/schedule/WeeklyScheduleFileExample.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/templates/schedule/WeeklyScheduleFileExample.java" } ] }, @@ -237,7 +237,7 @@ "tags": ["letter"], "pdf": "showcase/pdf/templates/coverletter/cover-letter-blue-banner-v2.pdf", "screenshot": "showcase/screenshots/templates/coverletter/cover-letter-blue-banner-v2.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvBlueBannerLetterV2Example.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvBlueBannerLetterV2Example.java" }, { "id": "cover-letter-boxed-sections-v2", @@ -246,7 +246,7 @@ "tags": ["letter"], "pdf": "showcase/pdf/templates/coverletter/cover-letter-boxed-sections-v2.pdf", "screenshot": "showcase/screenshots/templates/coverletter/cover-letter-boxed-sections-v2.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvBoxedSectionsLetterV2Example.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvBoxedSectionsLetterV2Example.java" }, { "id": "cover-letter-centered-headline-v2", @@ -255,7 +255,7 @@ "tags": ["letter"], "pdf": "showcase/pdf/templates/coverletter/cover-letter-centered-headline-v2.pdf", "screenshot": "showcase/screenshots/templates/coverletter/cover-letter-centered-headline-v2.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvCenteredHeadlineLetterV2Example.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvCenteredHeadlineLetterV2Example.java" }, { "id": "cover-letter-classic-serif-v2", @@ -264,7 +264,7 @@ "tags": ["letter"], "pdf": "showcase/pdf/templates/coverletter/cover-letter-classic-serif-v2.pdf", "screenshot": "showcase/screenshots/templates/coverletter/cover-letter-classic-serif-v2.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvClassicSerifLetterV2Example.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvClassicSerifLetterV2Example.java" }, { "id": "cover-letter-compact-mono-v2", @@ -273,7 +273,7 @@ "tags": ["letter"], "pdf": "showcase/pdf/templates/coverletter/cover-letter-compact-mono-v2.pdf", "screenshot": "showcase/screenshots/templates/coverletter/cover-letter-compact-mono-v2.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvCompactMonoLetterV2Example.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvCompactMonoLetterV2Example.java" }, { "id": "cover-letter-editorial-blue-v2", @@ -282,7 +282,7 @@ "tags": ["letter"], "pdf": "showcase/pdf/templates/coverletter/cover-letter-editorial-blue-v2.pdf", "screenshot": "showcase/screenshots/templates/coverletter/cover-letter-editorial-blue-v2.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvEditorialBlueLetterV2Example.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvEditorialBlueLetterV2Example.java" }, { "id": "cover-letter-engineering-resume-v2", @@ -291,7 +291,7 @@ "tags": ["letter"], "pdf": "showcase/pdf/templates/coverletter/cover-letter-engineering-resume-v2.pdf", "screenshot": "showcase/screenshots/templates/coverletter/cover-letter-engineering-resume-v2.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvEngineeringResumeLetterV2Example.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvEngineeringResumeLetterV2Example.java" }, { "id": "cover-letter-executive-v2", @@ -300,7 +300,7 @@ "tags": ["letter"], "pdf": "showcase/pdf/templates/coverletter/cover-letter-executive-v2.pdf", "screenshot": "showcase/screenshots/templates/coverletter/cover-letter-executive-v2.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvExecutiveLetterV2Example.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvExecutiveLetterV2Example.java" }, { "id": "cover-letter-mint-editorial-v2", @@ -309,7 +309,7 @@ "tags": ["letter"], "pdf": "showcase/pdf/templates/coverletter/cover-letter-mint-editorial-v2.pdf", "screenshot": "showcase/screenshots/templates/coverletter/cover-letter-mint-editorial-v2.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvMintEditorialLetterV2Example.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvMintEditorialLetterV2Example.java" }, { "id": "cover-letter-modern-professional-v2", @@ -318,7 +318,7 @@ "tags": ["letter"], "pdf": "showcase/pdf/templates/coverletter/cover-letter-modern-professional-v2.pdf", "screenshot": "showcase/screenshots/templates/coverletter/cover-letter-modern-professional-v2.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvModernProfessionalLetterV2Example.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvModernProfessionalLetterV2Example.java" }, { "id": "cover-letter-monogram-sidebar-v2", @@ -327,7 +327,7 @@ "tags": ["letter"], "pdf": "showcase/pdf/templates/coverletter/cover-letter-monogram-sidebar-v2.pdf", "screenshot": "showcase/screenshots/templates/coverletter/cover-letter-monogram-sidebar-v2.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvMonogramSidebarLetterV2Example.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvMonogramSidebarLetterV2Example.java" }, { "id": "cover-letter-nordic-clean-v2", @@ -336,7 +336,7 @@ "tags": ["letter"], "pdf": "showcase/pdf/templates/coverletter/cover-letter-nordic-clean-v2.pdf", "screenshot": "showcase/screenshots/templates/coverletter/cover-letter-nordic-clean-v2.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvNordicCleanLetterV2Example.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvNordicCleanLetterV2Example.java" }, { "id": "cover-letter-panel-v2", @@ -345,7 +345,7 @@ "tags": ["letter"], "pdf": "showcase/pdf/templates/coverletter/cover-letter-panel-v2.pdf", "screenshot": "showcase/screenshots/templates/coverletter/cover-letter-panel-v2.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvPanelLetterV2Example.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvPanelLetterV2Example.java" }, { "id": "cover-letter-sidebar-portrait-v2", @@ -354,7 +354,7 @@ "tags": ["letter"], "pdf": "showcase/pdf/templates/coverletter/cover-letter-sidebar-portrait-v2.pdf", "screenshot": "showcase/screenshots/templates/coverletter/cover-letter-sidebar-portrait-v2.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvSidebarPortraitLetterV2Example.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvSidebarPortraitLetterV2Example.java" }, { "id": "cover-letter-timeline-minimal-v2", @@ -363,7 +363,7 @@ "tags": ["letter"], "pdf": "showcase/pdf/templates/coverletter/cover-letter-timeline-minimal-v2.pdf", "screenshot": "showcase/screenshots/templates/coverletter/cover-letter-timeline-minimal-v2.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvTimelineMinimalLetterV2Example.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvTimelineMinimalLetterV2Example.java" } ] } @@ -384,7 +384,7 @@ "tags": ["canvas", "canvas", "v1.6", "absolute"], "pdf": "showcase/pdf/features/canvas/canvas-layer-showcase.pdf", "screenshot": "showcase/screenshots/features/canvas/canvas-layer-showcase.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/features/canvas/CanvasLayerExample.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/features/canvas/CanvasLayerExample.java" } ] }, @@ -399,7 +399,7 @@ "tags": ["tables", "tables", "v1.6"], "pdf": "showcase/pdf/features/tables/composed-table-cell-showcase.pdf", "screenshot": "showcase/screenshots/features/tables/composed-table-cell-showcase.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/features/tables/ComposedTableCellExample.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/features/tables/ComposedTableCellExample.java" }, { "id": "table-advanced", @@ -408,7 +408,7 @@ "tags": ["tables", "tables", "pagination"], "pdf": "showcase/pdf/features/tables/table-advanced.pdf", "screenshot": "showcase/screenshots/features/tables/table-advanced.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/features/tables/TableAdvancedExample.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/features/tables/TableAdvancedExample.java" } ] }, @@ -423,7 +423,7 @@ "tags": ["lists", "lists", "v1.6"], "pdf": "showcase/pdf/features/lists/nested-list-showcase.pdf", "screenshot": "showcase/screenshots/features/lists/nested-list-showcase.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/features/lists/NestedListExample.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/features/lists/NestedListExample.java" } ] }, @@ -431,6 +431,24 @@ "id": "shapes", "label": "Shapes & Containers", "examples": [ + { + "id": "line-cap", + "title": "Line Cap", + "description": "Generated showcase for features / shapes.", + "tags": ["features", "shapes"], + "pdf": "showcase/pdf/features/shapes/line-cap.pdf", + "screenshot": "showcase/screenshots/features/shapes/line-cap.png", + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples" + }, + { + "id": "line-fill", + "title": "Line Fill", + "description": "Generated showcase for features / shapes.", + "tags": ["features", "shapes"], + "pdf": "showcase/pdf/features/shapes/line-fill.pdf", + "screenshot": "showcase/screenshots/features/shapes/line-fill.png", + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples" + }, { "id": "photo-clip", "title": "Photo Clip (silhouette)", @@ -438,7 +456,7 @@ "tags": ["shapes", "shapes", "clip", "v1.8"], "pdf": "showcase/pdf/features/shapes/photo-clip.pdf", "screenshot": "showcase/screenshots/features/shapes/photo-clip.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/features/shapes/PhotoClipExample.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/features/shapes/PhotoClipExample.java" }, { "id": "shape-container", @@ -447,7 +465,7 @@ "tags": ["shapes", "shapes", "clip"], "pdf": "showcase/pdf/features/shapes/shape-container.pdf", "screenshot": "showcase/screenshots/features/shapes/shape-container.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/features/shapes/ShapeContainerExample.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/features/shapes/ShapeContainerExample.java" }, { "id": "vector-path", @@ -456,7 +474,7 @@ "tags": ["shapes", "shapes", "bezier", "v1.8"], "pdf": "showcase/pdf/features/shapes/vector-path.pdf", "screenshot": "showcase/screenshots/features/shapes/vector-path.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/features/shapes/ShapeContainerExample.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/features/shapes/ShapeContainerExample.java" } ] }, @@ -471,7 +489,7 @@ "tags": ["transforms", "transforms", "layers"], "pdf": "showcase/pdf/features/transforms/transforms.pdf", "screenshot": "showcase/screenshots/features/transforms/transforms.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/features/transforms/TransformsExample.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/features/transforms/TransformsExample.java" } ] }, @@ -479,6 +497,51 @@ "id": "text", "label": "Rich Text", "examples": [ + { + "id": "emoji-clip-path", + "title": "Emoji Clip Path", + "description": "Generated showcase for features / text.", + "tags": ["features", "text"], + "pdf": "showcase/pdf/features/text/emoji-clip-path.pdf", + "screenshot": "showcase/screenshots/features/text/emoji-clip-path.png", + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples" + }, + { + "id": "emoji-gallery", + "title": "Emoji Gallery", + "description": "Generated showcase for features / text.", + "tags": ["features", "text"], + "pdf": "showcase/pdf/features/text/emoji-gallery.pdf", + "screenshot": "showcase/screenshots/features/text/emoji-gallery.png", + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples" + }, + { + "id": "emoji-shortcodes", + "title": "Emoji Shortcodes", + "description": "Generated showcase for features / text.", + "tags": ["features", "text"], + "pdf": "showcase/pdf/features/text/emoji-shortcodes.pdf", + "screenshot": "showcase/screenshots/features/text/emoji-shortcodes.png", + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples" + }, + { + "id": "emoji-svg-vs-png", + "title": "Emoji Svg Vs Png", + "description": "Generated showcase for features / text.", + "tags": ["features", "text"], + "pdf": "showcase/pdf/features/text/emoji-svg-vs-png.pdf", + "screenshot": "showcase/screenshots/features/text/emoji-svg-vs-png.png", + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples" + }, + { + "id": "inline-highlight-chips", + "title": "Inline Highlight Chips", + "description": "Generated showcase for features / text.", + "tags": ["features", "text"], + "pdf": "showcase/pdf/features/text/inline-highlight-chips.pdf", + "screenshot": "showcase/screenshots/features/text/inline-highlight-chips.png", + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples" + }, { "id": "inline-shapes", "title": "Inline Shapes", @@ -486,7 +549,16 @@ "tags": ["features", "text"], "pdf": "showcase/pdf/features/text/inline-shapes.pdf", "screenshot": "showcase/screenshots/features/text/inline-shapes.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples" + }, + { + "id": "inline-svg-icons", + "title": "Inline Svg Icons", + "description": "Generated showcase for features / text.", + "tags": ["features", "text"], + "pdf": "showcase/pdf/features/text/inline-svg-icons.pdf", + "screenshot": "showcase/screenshots/features/text/inline-svg-icons.png", + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples" }, { "id": "rich-text-showcase", @@ -495,7 +567,7 @@ "tags": ["text", "text", "rich"], "pdf": "showcase/pdf/features/text/rich-text-showcase.pdf", "screenshot": "showcase/screenshots/features/text/rich-text-showcase.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/features/text/RichTextShowcaseExample.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/features/text/RichTextShowcaseExample.java" }, { "id": "section-presets", @@ -504,7 +576,7 @@ "tags": ["text", "text", "sections"], "pdf": "showcase/pdf/features/text/section-presets.pdf", "screenshot": "showcase/screenshots/features/text/section-presets.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/features/text/SectionPresetsExample.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/features/text/SectionPresetsExample.java" } ] }, @@ -519,7 +591,7 @@ "tags": ["themes", "themes"], "pdf": "showcase/pdf/features/themes/invoice-custom-theme.pdf", "screenshot": "showcase/screenshots/features/themes/invoice-custom-theme.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/features/themes/CustomBusinessThemeExample.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/features/themes/CustomBusinessThemeExample.java" } ] }, @@ -534,7 +606,7 @@ "tags": ["barcodes", "barcodes", "qr"], "pdf": "showcase/pdf/features/barcodes/barcode-showcase.pdf", "screenshot": "showcase/screenshots/features/barcodes/barcode-showcase.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/features/barcodes/BarcodeShowcaseExample.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/features/barcodes/BarcodeShowcaseExample.java" } ] }, @@ -542,6 +614,15 @@ "id": "chrome", "label": "PDF Chrome (header / footer / watermark)", "examples": [ + { + "id": "page-numbering", + "title": "Page Numbering", + "description": "Generated showcase for features / chrome.", + "tags": ["features", "chrome"], + "pdf": "showcase/pdf/features/chrome/page-numbering.pdf", + "screenshot": "showcase/screenshots/features/chrome/page-numbering.png", + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples" + }, { "id": "pdf-chrome", "title": "PDF Chrome", @@ -549,7 +630,16 @@ "tags": ["chrome", "chrome", "metadata", "watermark"], "pdf": "showcase/pdf/features/chrome/pdf-chrome.pdf", "screenshot": "showcase/screenshots/features/chrome/pdf-chrome.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/features/chrome/PdfChromeExample.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/features/chrome/PdfChromeExample.java" + }, + { + "id": "viewer-preferences", + "title": "Viewer Preferences", + "description": "Generated showcase for features / chrome.", + "tags": ["features", "chrome"], + "pdf": "showcase/pdf/features/chrome/viewer-preferences.pdf", + "screenshot": "showcase/screenshots/features/chrome/viewer-preferences.png", + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples" } ] }, @@ -564,7 +654,7 @@ "tags": ["streaming", "streaming", "http"], "pdf": "showcase/pdf/features/streaming/invoice-http-stream.pdf", "screenshot": "showcase/screenshots/features/streaming/invoice-http-stream.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/features/streaming/HttpStreamingExample.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/features/streaming/HttpStreamingExample.java" } ] }, @@ -579,7 +669,7 @@ "tags": ["snapshots", "snapshots", "testing"], "pdf": "showcase/pdf/features/snapshots/invoice-snapshot-regression.pdf", "screenshot": "showcase/screenshots/features/snapshots/invoice-snapshot-regression.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/features/snapshots/LayoutSnapshotRegressionExample.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/features/snapshots/LayoutSnapshotRegressionExample.java" } ] }, @@ -594,7 +684,7 @@ "tags": ["features", "charts"], "pdf": "showcase/pdf/features/charts/chart-showcase.pdf", "screenshot": "showcase/screenshots/features/charts/chart-showcase.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples" } ] }, @@ -609,7 +699,7 @@ "tags": ["debug", "debug", "labels", "v1.8"], "pdf": "showcase/pdf/features/debug/debug-overlay.pdf", "screenshot": "showcase/screenshots/features/debug/debug-overlay.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/features/debug/Debug Overlay" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/features/debug/Debug Overlay" } ] }, @@ -624,7 +714,7 @@ "tags": ["docx", "docx", "word", "export"], "pdf": "showcase/pdf/features/docx/word-export-companion.pdf", "screenshot": "showcase/screenshots/features/docx/word-export-companion.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/features/docx/WordExportExample.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/features/docx/WordExportExample.java" } ] }, @@ -639,7 +729,109 @@ "tags": ["layout", "layout", "align", "v1.8"], "pdf": "showcase/pdf/features/layout/block-align.pdf", "screenshot": "showcase/screenshots/features/layout/block-align.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/features/layout/Block Align" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/features/layout/Block Align" + }, + { + "id": "content-bleed", + "title": "Content Bleed", + "description": "Generated showcase for features / layout.", + "tags": ["features", "layout"], + "pdf": "showcase/pdf/features/layout/content-bleed.pdf", + "screenshot": "showcase/screenshots/features/layout/content-bleed.png", + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples" + }, + { + "id": "per-page-margin", + "title": "Per Page Margin", + "description": "Generated showcase for features / layout.", + "tags": ["features", "layout"], + "pdf": "showcase/pdf/features/layout/per-page-margin.pdf", + "screenshot": "showcase/screenshots/features/layout/per-page-margin.png", + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples" + }, + { + "id": "row-columns", + "title": "Row Columns", + "description": "Generated showcase for features / layout.", + "tags": ["features", "layout"], + "pdf": "showcase/pdf/features/layout/row-columns.pdf", + "screenshot": "showcase/screenshots/features/layout/row-columns.png", + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples" + }, + { + "id": "row-flex", + "title": "Row Flex", + "description": "Generated showcase for features / layout.", + "tags": ["features", "layout"], + "pdf": "showcase/pdf/features/layout/row-flex.pdf", + "screenshot": "showcase/screenshots/features/layout/row-flex.png", + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples" + }, + { + "id": "row-vertical-align", + "title": "Row Vertical Align", + "description": "Generated showcase for features / layout.", + "tags": ["features", "layout"], + "pdf": "showcase/pdf/features/layout/row-vertical-align.pdf", + "screenshot": "showcase/screenshots/features/layout/row-vertical-align.png", + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples" + } + ] + }, + { + "id": "navigation", + "label": "Navigation", + "examples": [ + { + "id": "container-bookmark", + "title": "Container Bookmark", + "description": "Generated showcase for features / navigation.", + "tags": ["features", "navigation"], + "pdf": "showcase/pdf/features/navigation/container-bookmark.pdf", + "screenshot": "showcase/screenshots/features/navigation/container-bookmark.png", + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples" + }, + { + "id": "in-pdf-navigation", + "title": "In Pdf Navigation", + "description": "Generated showcase for features / navigation.", + "tags": ["features", "navigation"], + "pdf": "showcase/pdf/features/navigation/in-pdf-navigation.pdf", + "screenshot": "showcase/screenshots/features/navigation/in-pdf-navigation.png", + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples" + }, + { + "id": "page-reference", + "title": "Page Reference", + "description": "Generated showcase for features / navigation.", + "tags": ["features", "navigation"], + "pdf": "showcase/pdf/features/navigation/page-reference.pdf", + "screenshot": "showcase/screenshots/features/navigation/page-reference.png", + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples" + }, + { + "id": "table-of-contents", + "title": "Table Of Contents", + "description": "Generated showcase for features / navigation.", + "tags": ["features", "navigation"], + "pdf": "showcase/pdf/features/navigation/table-of-contents.pdf", + "screenshot": "showcase/screenshots/features/navigation/table-of-contents.png", + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples" + } + ] + }, + { + "id": "structure", + "label": "Structure", + "examples": [ + { + "id": "multi-section-document", + "title": "Multi Section Document", + "description": "Generated showcase for features / structure.", + "tags": ["features", "structure"], + "pdf": "showcase/pdf/features/structure/multi-section-document.pdf", + "screenshot": "showcase/screenshots/features/structure/multi-section-document.png", + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples" } ] }, @@ -654,7 +846,22 @@ "tags": ["svg", "svg", "icons", "v1.8"], "pdf": "showcase/pdf/features/svg/svg-icon-gallery.pdf", "screenshot": "showcase/screenshots/features/svg/svg-icon-gallery.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/features/svg/Svg Icon Gallery" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/features/svg/Svg Icon Gallery" + } + ] + }, + { + "id": "title", + "label": "Title", + "examples": [ + { + "id": "book-template", + "title": "Book Template", + "description": "Generated showcase for features / title.", + "tags": ["features", "title"], + "pdf": "showcase/pdf/features/title/book-template.pdf", + "screenshot": "showcase/screenshots/features/title/book-template.png", + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples" } ] } @@ -675,7 +882,7 @@ "tags": ["flagship", "showcase", "cover"], "pdf": "showcase/pdf/flagships/default/business-report.pdf", "screenshot": "showcase/screenshots/flagships/default/business-report.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/flagships/BusinessReportExample.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/flagships/BusinessReportExample.java" }, { "id": "engine-deck", @@ -684,7 +891,7 @@ "tags": ["flagships", "default"], "pdf": "showcase/pdf/flagships/default/engine-deck.pdf", "screenshot": "showcase/screenshots/flagships/default/engine-deck.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples" }, { "id": "feature-catalog", @@ -693,7 +900,7 @@ "tags": ["flagships", "default"], "pdf": "showcase/pdf/flagships/default/feature-catalog.pdf", "screenshot": "showcase/screenshots/flagships/default/feature-catalog.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples" }, { "id": "financial-report", @@ -702,7 +909,7 @@ "tags": ["flagships", "default"], "pdf": "showcase/pdf/flagships/default/financial-report.pdf", "screenshot": "showcase/screenshots/flagships/default/financial-report.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples" }, { "id": "master-showcase", @@ -711,7 +918,7 @@ "tags": ["flagship", "showcase"], "pdf": "showcase/pdf/flagships/default/master-showcase.pdf", "screenshot": "showcase/screenshots/flagships/default/master-showcase.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/flagships/MasterShowcaseExample.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/flagships/MasterShowcaseExample.java" }, { "id": "module-first-profile", @@ -720,7 +927,7 @@ "tags": ["flagship", "authoring"], "pdf": "showcase/pdf/flagships/default/module-first-profile.pdf", "screenshot": "showcase/screenshots/flagships/default/module-first-profile.png", - "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/flagships/ModuleFirstFileExample.java" + "code": "https://github.com/DemchaAV/GraphCompose/blob/v1.9.0/examples/src/main/java/com/demcha/examples/flagships/ModuleFirstFileExample.java" } ] } diff --git a/web/index.html b/web/index.html index 3844365c4..acfb32427 100644 --- a/web/index.html +++ b/web/index.html @@ -57,9 +57,9 @@ "applicationSubCategory": "Library", "operatingSystem": "Cross-platform (JVM 21+)", "programmingLanguage": "Java", - "softwareVersion": "1.8.0", + "softwareVersion": "1.9.0", "url": "https://demchaav.github.io/GraphCompose/", - "downloadUrl": "https://central.sonatype.com/artifact/io.github.demchaav/graph-compose/1.8.0", + "downloadUrl": "https://central.sonatype.com/artifact/io.github.demchaav/graph-compose/1.9.0", "image": "https://demchaav.github.io/GraphCompose/assets/logo/graphcompose-logo.png", "license": "https://github.com/DemchaAV/GraphCompose/blob/main/LICENSE", "author": { @@ -168,7 +168,7 @@
-

Java · v1.8.0 · MIT

+

Java · v1.9.0 · MIT

Java PDF layout engine for structured business documents.

Describe documents. Render polished PDFs. A semantic layout engine for Java services that need structured, paginated, theme-driven output — CVs, invoices, proposals, reports.

No drawing API. No pixel arithmetic. You compose ParagraphNode, TableNode, SectionNode; GraphCompose handles measurement, pagination, fonts, and PDFBox rendering.

@@ -233,14 +233,14 @@

Maven

<dependency>
   <groupId>io.github.demchaav</groupId>
   <artifactId>graph-compose</artifactId>
-  <version>1.8.0</version>
+  <version>1.9.0</version>
 </dependency>

Gradle

dependencies {
   implementation(
-    'io.github.demchaav:graph-compose:1.8.0'
+    'io.github.demchaav:graph-compose:1.9.0'
   )
 }
diff --git a/web/showcase/pdf/features/barcodes/barcode-showcase.pdf b/web/showcase/pdf/features/barcodes/barcode-showcase.pdf index 02e5d26f4..cdd035316 100644 Binary files a/web/showcase/pdf/features/barcodes/barcode-showcase.pdf and b/web/showcase/pdf/features/barcodes/barcode-showcase.pdf differ diff --git a/web/showcase/pdf/features/canvas/canvas-layer-showcase.pdf b/web/showcase/pdf/features/canvas/canvas-layer-showcase.pdf index ad7a855c1..3e4896a29 100644 Binary files a/web/showcase/pdf/features/canvas/canvas-layer-showcase.pdf and b/web/showcase/pdf/features/canvas/canvas-layer-showcase.pdf differ diff --git a/web/showcase/pdf/features/chrome/pdf-chrome.pdf b/web/showcase/pdf/features/chrome/pdf-chrome.pdf index 8da3ab702..6ec0f64d5 100644 Binary files a/web/showcase/pdf/features/chrome/pdf-chrome.pdf and b/web/showcase/pdf/features/chrome/pdf-chrome.pdf differ diff --git a/web/showcase/pdf/features/lists/nested-list-showcase.pdf b/web/showcase/pdf/features/lists/nested-list-showcase.pdf index c759ea3d1..019357cb1 100644 Binary files a/web/showcase/pdf/features/lists/nested-list-showcase.pdf and b/web/showcase/pdf/features/lists/nested-list-showcase.pdf differ diff --git a/web/showcase/pdf/features/shapes/shape-container.pdf b/web/showcase/pdf/features/shapes/shape-container.pdf index d815aad34..c3b00965d 100644 Binary files a/web/showcase/pdf/features/shapes/shape-container.pdf and b/web/showcase/pdf/features/shapes/shape-container.pdf differ diff --git a/web/showcase/pdf/features/snapshots/invoice-snapshot-regression.pdf b/web/showcase/pdf/features/snapshots/invoice-snapshot-regression.pdf index 749a55bf7..9beb876ea 100644 Binary files a/web/showcase/pdf/features/snapshots/invoice-snapshot-regression.pdf and b/web/showcase/pdf/features/snapshots/invoice-snapshot-regression.pdf differ diff --git a/web/showcase/pdf/features/streaming/invoice-http-stream.pdf b/web/showcase/pdf/features/streaming/invoice-http-stream.pdf index 737fa9f3e..3fc309b69 100644 Binary files a/web/showcase/pdf/features/streaming/invoice-http-stream.pdf and b/web/showcase/pdf/features/streaming/invoice-http-stream.pdf differ diff --git a/web/showcase/pdf/features/tables/composed-table-cell-showcase.pdf b/web/showcase/pdf/features/tables/composed-table-cell-showcase.pdf index cebfd68e0..97d31e47c 100644 Binary files a/web/showcase/pdf/features/tables/composed-table-cell-showcase.pdf and b/web/showcase/pdf/features/tables/composed-table-cell-showcase.pdf differ diff --git a/web/showcase/pdf/features/tables/table-advanced.pdf b/web/showcase/pdf/features/tables/table-advanced.pdf index c6051047d..8c233fa6c 100644 Binary files a/web/showcase/pdf/features/tables/table-advanced.pdf and b/web/showcase/pdf/features/tables/table-advanced.pdf differ diff --git a/web/showcase/pdf/features/text/rich-text-showcase.pdf b/web/showcase/pdf/features/text/rich-text-showcase.pdf index 6a4e91552..151c761af 100644 Binary files a/web/showcase/pdf/features/text/rich-text-showcase.pdf and b/web/showcase/pdf/features/text/rich-text-showcase.pdf differ diff --git a/web/showcase/pdf/features/text/section-presets.pdf b/web/showcase/pdf/features/text/section-presets.pdf index 8eda09571..bf1930848 100644 Binary files a/web/showcase/pdf/features/text/section-presets.pdf and b/web/showcase/pdf/features/text/section-presets.pdf differ diff --git a/web/showcase/pdf/features/themes/invoice-custom-theme.pdf b/web/showcase/pdf/features/themes/invoice-custom-theme.pdf index b18faf470..b8526be12 100644 Binary files a/web/showcase/pdf/features/themes/invoice-custom-theme.pdf and b/web/showcase/pdf/features/themes/invoice-custom-theme.pdf differ diff --git a/web/showcase/pdf/features/transforms/transforms.pdf b/web/showcase/pdf/features/transforms/transforms.pdf index 7d7f13e9d..26d459c18 100644 Binary files a/web/showcase/pdf/features/transforms/transforms.pdf and b/web/showcase/pdf/features/transforms/transforms.pdf differ diff --git a/web/showcase/pdf/flagships/default/business-report.pdf b/web/showcase/pdf/flagships/default/business-report.pdf index 29377b16a..08ea8c7ab 100644 Binary files a/web/showcase/pdf/flagships/default/business-report.pdf and b/web/showcase/pdf/flagships/default/business-report.pdf differ diff --git a/web/showcase/pdf/flagships/default/master-showcase.pdf b/web/showcase/pdf/flagships/default/master-showcase.pdf index 040d8d197..6cf9d1617 100644 Binary files a/web/showcase/pdf/flagships/default/master-showcase.pdf and b/web/showcase/pdf/flagships/default/master-showcase.pdf differ diff --git a/web/showcase/pdf/flagships/default/module-first-profile.pdf b/web/showcase/pdf/flagships/default/module-first-profile.pdf index 292f6f50b..6f91fd167 100644 Binary files a/web/showcase/pdf/flagships/default/module-first-profile.pdf and b/web/showcase/pdf/flagships/default/module-first-profile.pdf differ diff --git a/web/showcase/pdf/templates/coverletter/cover-letter-blue-banner-v2.pdf b/web/showcase/pdf/templates/coverletter/cover-letter-blue-banner-v2.pdf index 8dbefa552..2354064a9 100644 Binary files a/web/showcase/pdf/templates/coverletter/cover-letter-blue-banner-v2.pdf and b/web/showcase/pdf/templates/coverletter/cover-letter-blue-banner-v2.pdf differ diff --git a/web/showcase/pdf/templates/coverletter/cover-letter-boxed-sections-v2.pdf b/web/showcase/pdf/templates/coverletter/cover-letter-boxed-sections-v2.pdf index 2dd4c4fc0..a5d6847e4 100644 Binary files a/web/showcase/pdf/templates/coverletter/cover-letter-boxed-sections-v2.pdf and b/web/showcase/pdf/templates/coverletter/cover-letter-boxed-sections-v2.pdf differ diff --git a/web/showcase/pdf/templates/coverletter/cover-letter-centered-headline-v2.pdf b/web/showcase/pdf/templates/coverletter/cover-letter-centered-headline-v2.pdf index 0b17d8a70..b3cf2d728 100644 Binary files a/web/showcase/pdf/templates/coverletter/cover-letter-centered-headline-v2.pdf and b/web/showcase/pdf/templates/coverletter/cover-letter-centered-headline-v2.pdf differ diff --git a/web/showcase/pdf/templates/coverletter/cover-letter-classic-serif-v2.pdf b/web/showcase/pdf/templates/coverletter/cover-letter-classic-serif-v2.pdf index 7815ec101..6fec12e89 100644 Binary files a/web/showcase/pdf/templates/coverletter/cover-letter-classic-serif-v2.pdf and b/web/showcase/pdf/templates/coverletter/cover-letter-classic-serif-v2.pdf differ diff --git a/web/showcase/pdf/templates/coverletter/cover-letter-compact-mono-v2.pdf b/web/showcase/pdf/templates/coverletter/cover-letter-compact-mono-v2.pdf index 0092ee1ef..681e0465c 100644 Binary files a/web/showcase/pdf/templates/coverletter/cover-letter-compact-mono-v2.pdf and b/web/showcase/pdf/templates/coverletter/cover-letter-compact-mono-v2.pdf differ diff --git a/web/showcase/pdf/templates/coverletter/cover-letter-editorial-blue-v2.pdf b/web/showcase/pdf/templates/coverletter/cover-letter-editorial-blue-v2.pdf index 27112c2fe..550a6e068 100644 Binary files a/web/showcase/pdf/templates/coverletter/cover-letter-editorial-blue-v2.pdf and b/web/showcase/pdf/templates/coverletter/cover-letter-editorial-blue-v2.pdf differ diff --git a/web/showcase/pdf/templates/coverletter/cover-letter-engineering-resume-v2.pdf b/web/showcase/pdf/templates/coverletter/cover-letter-engineering-resume-v2.pdf index ee1c5c8c4..f8082f661 100644 Binary files a/web/showcase/pdf/templates/coverletter/cover-letter-engineering-resume-v2.pdf and b/web/showcase/pdf/templates/coverletter/cover-letter-engineering-resume-v2.pdf differ diff --git a/web/showcase/pdf/templates/coverletter/cover-letter-executive-v2.pdf b/web/showcase/pdf/templates/coverletter/cover-letter-executive-v2.pdf index 49c89d354..14dd114be 100644 Binary files a/web/showcase/pdf/templates/coverletter/cover-letter-executive-v2.pdf and b/web/showcase/pdf/templates/coverletter/cover-letter-executive-v2.pdf differ diff --git a/web/showcase/pdf/templates/coverletter/cover-letter-mint-editorial-v2.pdf b/web/showcase/pdf/templates/coverletter/cover-letter-mint-editorial-v2.pdf index 3441a2b30..86057767e 100644 Binary files a/web/showcase/pdf/templates/coverletter/cover-letter-mint-editorial-v2.pdf and b/web/showcase/pdf/templates/coverletter/cover-letter-mint-editorial-v2.pdf differ diff --git a/web/showcase/pdf/templates/coverletter/cover-letter-modern-professional-v2.pdf b/web/showcase/pdf/templates/coverletter/cover-letter-modern-professional-v2.pdf index 8a4453962..8c15b7ad4 100644 Binary files a/web/showcase/pdf/templates/coverletter/cover-letter-modern-professional-v2.pdf and b/web/showcase/pdf/templates/coverletter/cover-letter-modern-professional-v2.pdf differ diff --git a/web/showcase/pdf/templates/coverletter/cover-letter-monogram-sidebar-v2.pdf b/web/showcase/pdf/templates/coverletter/cover-letter-monogram-sidebar-v2.pdf index 7ebeac915..6ac32d7b8 100644 Binary files a/web/showcase/pdf/templates/coverletter/cover-letter-monogram-sidebar-v2.pdf and b/web/showcase/pdf/templates/coverletter/cover-letter-monogram-sidebar-v2.pdf differ diff --git a/web/showcase/pdf/templates/coverletter/cover-letter-nordic-clean-v2.pdf b/web/showcase/pdf/templates/coverletter/cover-letter-nordic-clean-v2.pdf index fe240c5ed..fc21513a1 100644 Binary files a/web/showcase/pdf/templates/coverletter/cover-letter-nordic-clean-v2.pdf and b/web/showcase/pdf/templates/coverletter/cover-letter-nordic-clean-v2.pdf differ diff --git a/web/showcase/pdf/templates/coverletter/cover-letter-panel-v2.pdf b/web/showcase/pdf/templates/coverletter/cover-letter-panel-v2.pdf index d19272d72..7b650fbdd 100644 Binary files a/web/showcase/pdf/templates/coverletter/cover-letter-panel-v2.pdf and b/web/showcase/pdf/templates/coverletter/cover-letter-panel-v2.pdf differ diff --git a/web/showcase/pdf/templates/coverletter/cover-letter-sidebar-portrait-v2.pdf b/web/showcase/pdf/templates/coverletter/cover-letter-sidebar-portrait-v2.pdf index e5584fbd9..c1d89a7f5 100644 Binary files a/web/showcase/pdf/templates/coverletter/cover-letter-sidebar-portrait-v2.pdf and b/web/showcase/pdf/templates/coverletter/cover-letter-sidebar-portrait-v2.pdf differ diff --git a/web/showcase/pdf/templates/coverletter/cover-letter-timeline-minimal-v2.pdf b/web/showcase/pdf/templates/coverletter/cover-letter-timeline-minimal-v2.pdf index 81802b954..c3e62dbb8 100644 Binary files a/web/showcase/pdf/templates/coverletter/cover-letter-timeline-minimal-v2.pdf and b/web/showcase/pdf/templates/coverletter/cover-letter-timeline-minimal-v2.pdf differ diff --git a/web/showcase/pdf/templates/cv/cv-blue-banner-v2.pdf b/web/showcase/pdf/templates/cv/cv-blue-banner-v2.pdf index b5404102f..100bf00d5 100644 Binary files a/web/showcase/pdf/templates/cv/cv-blue-banner-v2.pdf and b/web/showcase/pdf/templates/cv/cv-blue-banner-v2.pdf differ diff --git a/web/showcase/pdf/templates/cv/cv-boxed-sections-v2.pdf b/web/showcase/pdf/templates/cv/cv-boxed-sections-v2.pdf index 6e51afe98..7a5eae616 100644 Binary files a/web/showcase/pdf/templates/cv/cv-boxed-sections-v2.pdf and b/web/showcase/pdf/templates/cv/cv-boxed-sections-v2.pdf differ diff --git a/web/showcase/pdf/templates/cv/cv-centered-headline-v2.pdf b/web/showcase/pdf/templates/cv/cv-centered-headline-v2.pdf index 4e1914144..a30263c10 100644 Binary files a/web/showcase/pdf/templates/cv/cv-centered-headline-v2.pdf and b/web/showcase/pdf/templates/cv/cv-centered-headline-v2.pdf differ diff --git a/web/showcase/pdf/templates/cv/cv-classic-serif-v2.pdf b/web/showcase/pdf/templates/cv/cv-classic-serif-v2.pdf index b35986c87..c3e111e8e 100644 Binary files a/web/showcase/pdf/templates/cv/cv-classic-serif-v2.pdf and b/web/showcase/pdf/templates/cv/cv-classic-serif-v2.pdf differ diff --git a/web/showcase/pdf/templates/cv/cv-compact-mono-v2.pdf b/web/showcase/pdf/templates/cv/cv-compact-mono-v2.pdf index 580a7928f..81ed90bf1 100644 Binary files a/web/showcase/pdf/templates/cv/cv-compact-mono-v2.pdf and b/web/showcase/pdf/templates/cv/cv-compact-mono-v2.pdf differ diff --git a/web/showcase/pdf/templates/cv/cv-editorial-blue-v2.pdf b/web/showcase/pdf/templates/cv/cv-editorial-blue-v2.pdf index 3cab2f0b0..676d101bd 100644 Binary files a/web/showcase/pdf/templates/cv/cv-editorial-blue-v2.pdf and b/web/showcase/pdf/templates/cv/cv-editorial-blue-v2.pdf differ diff --git a/web/showcase/pdf/templates/cv/cv-engineering-resume-v2.pdf b/web/showcase/pdf/templates/cv/cv-engineering-resume-v2.pdf index 34fc9f487..f83e49343 100644 Binary files a/web/showcase/pdf/templates/cv/cv-engineering-resume-v2.pdf and b/web/showcase/pdf/templates/cv/cv-engineering-resume-v2.pdf differ diff --git a/web/showcase/pdf/templates/cv/cv-executive-v2.pdf b/web/showcase/pdf/templates/cv/cv-executive-v2.pdf index f819d65b0..272fffc8f 100644 Binary files a/web/showcase/pdf/templates/cv/cv-executive-v2.pdf and b/web/showcase/pdf/templates/cv/cv-executive-v2.pdf differ diff --git a/web/showcase/pdf/templates/cv/cv-minimal-underlined-v2.pdf b/web/showcase/pdf/templates/cv/cv-minimal-underlined-v2.pdf index acc1ee1eb..9f15b6553 100644 Binary files a/web/showcase/pdf/templates/cv/cv-minimal-underlined-v2.pdf and b/web/showcase/pdf/templates/cv/cv-minimal-underlined-v2.pdf differ diff --git a/web/showcase/pdf/templates/cv/cv-mint-editorial-v2.pdf b/web/showcase/pdf/templates/cv/cv-mint-editorial-v2.pdf index f7b6a19f7..d8e43db23 100644 Binary files a/web/showcase/pdf/templates/cv/cv-mint-editorial-v2.pdf and b/web/showcase/pdf/templates/cv/cv-mint-editorial-v2.pdf differ diff --git a/web/showcase/pdf/templates/cv/cv-modern-professional-v2.pdf b/web/showcase/pdf/templates/cv/cv-modern-professional-v2.pdf index 7d8e5a086..6228c9972 100644 Binary files a/web/showcase/pdf/templates/cv/cv-modern-professional-v2.pdf and b/web/showcase/pdf/templates/cv/cv-modern-professional-v2.pdf differ diff --git a/web/showcase/pdf/templates/cv/cv-monogram-sidebar-v2.pdf b/web/showcase/pdf/templates/cv/cv-monogram-sidebar-v2.pdf index 8c8acf756..07c7c2b30 100644 Binary files a/web/showcase/pdf/templates/cv/cv-monogram-sidebar-v2.pdf and b/web/showcase/pdf/templates/cv/cv-monogram-sidebar-v2.pdf differ diff --git a/web/showcase/pdf/templates/cv/cv-nordic-clean-v2.pdf b/web/showcase/pdf/templates/cv/cv-nordic-clean-v2.pdf index 791743aee..a664be6cd 100644 Binary files a/web/showcase/pdf/templates/cv/cv-nordic-clean-v2.pdf and b/web/showcase/pdf/templates/cv/cv-nordic-clean-v2.pdf differ diff --git a/web/showcase/pdf/templates/cv/cv-panel-v2.pdf b/web/showcase/pdf/templates/cv/cv-panel-v2.pdf index 02a494309..f7b582bb1 100644 Binary files a/web/showcase/pdf/templates/cv/cv-panel-v2.pdf and b/web/showcase/pdf/templates/cv/cv-panel-v2.pdf differ diff --git a/web/showcase/pdf/templates/cv/cv-sidebar-portrait-v2.pdf b/web/showcase/pdf/templates/cv/cv-sidebar-portrait-v2.pdf index ed36f04f2..693ad4b58 100644 Binary files a/web/showcase/pdf/templates/cv/cv-sidebar-portrait-v2.pdf and b/web/showcase/pdf/templates/cv/cv-sidebar-portrait-v2.pdf differ diff --git a/web/showcase/pdf/templates/cv/cv-timeline-minimal-v2.pdf b/web/showcase/pdf/templates/cv/cv-timeline-minimal-v2.pdf index 8e3dcf227..eb864108b 100644 Binary files a/web/showcase/pdf/templates/cv/cv-timeline-minimal-v2.pdf and b/web/showcase/pdf/templates/cv/cv-timeline-minimal-v2.pdf differ diff --git a/web/showcase/pdf/templates/invoice/invoice-cinematic.pdf b/web/showcase/pdf/templates/invoice/invoice-cinematic.pdf index 04aa5b871..80696f00c 100644 Binary files a/web/showcase/pdf/templates/invoice/invoice-cinematic.pdf and b/web/showcase/pdf/templates/invoice/invoice-cinematic.pdf differ diff --git a/web/showcase/pdf/templates/invoice/invoice.pdf b/web/showcase/pdf/templates/invoice/invoice.pdf index 937326769..91cb652c6 100644 Binary files a/web/showcase/pdf/templates/invoice/invoice.pdf and b/web/showcase/pdf/templates/invoice/invoice.pdf differ diff --git a/web/showcase/pdf/templates/proposal/project-proposal-cinematic.pdf b/web/showcase/pdf/templates/proposal/project-proposal-cinematic.pdf index c8aeaf8d5..7cc887b1f 100644 Binary files a/web/showcase/pdf/templates/proposal/project-proposal-cinematic.pdf and b/web/showcase/pdf/templates/proposal/project-proposal-cinematic.pdf differ diff --git a/web/showcase/pdf/templates/proposal/proposal-cinematic.pdf b/web/showcase/pdf/templates/proposal/proposal-cinematic.pdf index 4e7bae40e..0d6fdce13 100644 Binary files a/web/showcase/pdf/templates/proposal/proposal-cinematic.pdf and b/web/showcase/pdf/templates/proposal/proposal-cinematic.pdf differ diff --git a/web/showcase/pdf/templates/proposal/proposal.pdf b/web/showcase/pdf/templates/proposal/proposal.pdf index 7f7b5279f..3d18c951b 100644 Binary files a/web/showcase/pdf/templates/proposal/proposal.pdf and b/web/showcase/pdf/templates/proposal/proposal.pdf differ diff --git a/web/showcase/pdf/templates/schedule/weekly-schedule.pdf b/web/showcase/pdf/templates/schedule/weekly-schedule.pdf index a2c160256..4a23dc2db 100644 Binary files a/web/showcase/pdf/templates/schedule/weekly-schedule.pdf and b/web/showcase/pdf/templates/schedule/weekly-schedule.pdf differ diff --git a/web/showcase/screenshots/features/chrome/page-numbering.png b/web/showcase/screenshots/features/chrome/page-numbering.png new file mode 100644 index 000000000..c76bbaa6f Binary files /dev/null and b/web/showcase/screenshots/features/chrome/page-numbering.png differ diff --git a/web/showcase/screenshots/features/chrome/pdf-chrome.png b/web/showcase/screenshots/features/chrome/pdf-chrome.png index a02a23ddd..040257062 100644 Binary files a/web/showcase/screenshots/features/chrome/pdf-chrome.png and b/web/showcase/screenshots/features/chrome/pdf-chrome.png differ diff --git a/web/showcase/screenshots/features/chrome/viewer-preferences.png b/web/showcase/screenshots/features/chrome/viewer-preferences.png new file mode 100644 index 000000000..e234b7ace Binary files /dev/null and b/web/showcase/screenshots/features/chrome/viewer-preferences.png differ diff --git a/web/showcase/screenshots/features/layout/content-bleed.png b/web/showcase/screenshots/features/layout/content-bleed.png new file mode 100644 index 000000000..40cc45af7 Binary files /dev/null and b/web/showcase/screenshots/features/layout/content-bleed.png differ diff --git a/web/showcase/screenshots/features/layout/per-page-margin.png b/web/showcase/screenshots/features/layout/per-page-margin.png new file mode 100644 index 000000000..ceaccd30c Binary files /dev/null and b/web/showcase/screenshots/features/layout/per-page-margin.png differ diff --git a/web/showcase/screenshots/features/layout/row-columns.png b/web/showcase/screenshots/features/layout/row-columns.png new file mode 100644 index 000000000..7a083d082 Binary files /dev/null and b/web/showcase/screenshots/features/layout/row-columns.png differ diff --git a/web/showcase/screenshots/features/layout/row-flex.png b/web/showcase/screenshots/features/layout/row-flex.png new file mode 100644 index 000000000..8ffc8849a Binary files /dev/null and b/web/showcase/screenshots/features/layout/row-flex.png differ diff --git a/web/showcase/screenshots/features/layout/row-vertical-align.png b/web/showcase/screenshots/features/layout/row-vertical-align.png new file mode 100644 index 000000000..7939646f0 Binary files /dev/null and b/web/showcase/screenshots/features/layout/row-vertical-align.png differ diff --git a/web/showcase/screenshots/features/navigation/container-bookmark.png b/web/showcase/screenshots/features/navigation/container-bookmark.png new file mode 100644 index 000000000..476925f73 Binary files /dev/null and b/web/showcase/screenshots/features/navigation/container-bookmark.png differ diff --git a/web/showcase/screenshots/features/navigation/in-pdf-navigation.png b/web/showcase/screenshots/features/navigation/in-pdf-navigation.png new file mode 100644 index 000000000..c4450d41a Binary files /dev/null and b/web/showcase/screenshots/features/navigation/in-pdf-navigation.png differ diff --git a/web/showcase/screenshots/features/navigation/page-reference.png b/web/showcase/screenshots/features/navigation/page-reference.png new file mode 100644 index 000000000..fda011548 Binary files /dev/null and b/web/showcase/screenshots/features/navigation/page-reference.png differ diff --git a/web/showcase/screenshots/features/navigation/table-of-contents.png b/web/showcase/screenshots/features/navigation/table-of-contents.png new file mode 100644 index 000000000..c06b650b8 Binary files /dev/null and b/web/showcase/screenshots/features/navigation/table-of-contents.png differ diff --git a/web/showcase/screenshots/features/shapes/line-cap.png b/web/showcase/screenshots/features/shapes/line-cap.png new file mode 100644 index 000000000..d4728b10e Binary files /dev/null and b/web/showcase/screenshots/features/shapes/line-cap.png differ diff --git a/web/showcase/screenshots/features/shapes/line-fill.png b/web/showcase/screenshots/features/shapes/line-fill.png new file mode 100644 index 000000000..ecf1976ec Binary files /dev/null and b/web/showcase/screenshots/features/shapes/line-fill.png differ diff --git a/web/showcase/screenshots/features/structure/multi-section-document.png b/web/showcase/screenshots/features/structure/multi-section-document.png new file mode 100644 index 000000000..e8dd29024 Binary files /dev/null and b/web/showcase/screenshots/features/structure/multi-section-document.png differ diff --git a/web/showcase/screenshots/features/svg/svg-icon-gallery.png b/web/showcase/screenshots/features/svg/svg-icon-gallery.png index dd33ae42a..35a2622d5 100644 Binary files a/web/showcase/screenshots/features/svg/svg-icon-gallery.png and b/web/showcase/screenshots/features/svg/svg-icon-gallery.png differ diff --git a/web/showcase/screenshots/features/text/emoji-clip-path.png b/web/showcase/screenshots/features/text/emoji-clip-path.png new file mode 100644 index 000000000..c48688d66 Binary files /dev/null and b/web/showcase/screenshots/features/text/emoji-clip-path.png differ diff --git a/web/showcase/screenshots/features/text/emoji-gallery.png b/web/showcase/screenshots/features/text/emoji-gallery.png new file mode 100644 index 000000000..2551d08eb Binary files /dev/null and b/web/showcase/screenshots/features/text/emoji-gallery.png differ diff --git a/web/showcase/screenshots/features/text/emoji-shortcodes.png b/web/showcase/screenshots/features/text/emoji-shortcodes.png new file mode 100644 index 000000000..34e5562a2 Binary files /dev/null and b/web/showcase/screenshots/features/text/emoji-shortcodes.png differ diff --git a/web/showcase/screenshots/features/text/emoji-svg-vs-png.png b/web/showcase/screenshots/features/text/emoji-svg-vs-png.png new file mode 100644 index 000000000..98ef37129 Binary files /dev/null and b/web/showcase/screenshots/features/text/emoji-svg-vs-png.png differ diff --git a/web/showcase/screenshots/features/text/inline-highlight-chips.png b/web/showcase/screenshots/features/text/inline-highlight-chips.png new file mode 100644 index 000000000..2e2303813 Binary files /dev/null and b/web/showcase/screenshots/features/text/inline-highlight-chips.png differ diff --git a/web/showcase/screenshots/features/text/inline-svg-icons.png b/web/showcase/screenshots/features/text/inline-svg-icons.png new file mode 100644 index 000000000..ba655ec81 Binary files /dev/null and b/web/showcase/screenshots/features/text/inline-svg-icons.png differ diff --git a/web/showcase/screenshots/features/title/book-template.png b/web/showcase/screenshots/features/title/book-template.png new file mode 100644 index 000000000..8d271ecc6 Binary files /dev/null and b/web/showcase/screenshots/features/title/book-template.png differ diff --git a/web/showcase/screenshots/flagships/default/engine-deck.png b/web/showcase/screenshots/flagships/default/engine-deck.png index 0471d773f..f493cbc5f 100644 Binary files a/web/showcase/screenshots/flagships/default/engine-deck.png and b/web/showcase/screenshots/flagships/default/engine-deck.png differ diff --git a/web/showcase/screenshots/flagships/default/feature-catalog.png b/web/showcase/screenshots/flagships/default/feature-catalog.png index 96ad05696..432417ba4 100644 Binary files a/web/showcase/screenshots/flagships/default/feature-catalog.png and b/web/showcase/screenshots/flagships/default/feature-catalog.png differ diff --git a/web/showcase/screenshots/flagships/default/master-showcase.png b/web/showcase/screenshots/flagships/default/master-showcase.png index c365454ed..f5ac16919 100644 Binary files a/web/showcase/screenshots/flagships/default/master-showcase.png and b/web/showcase/screenshots/flagships/default/master-showcase.png differ