Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,16 @@ PDF `GoTo` actions. External links are unchanged.
in the PDF backend. `PdfVisualRegression` also gains direct `renderPages(session)` /
`assertMatchesBaseline(name, session)` overloads on the same path.

### Deprecations

- **`templates.api.CoverLetterTemplate`** marked `@Deprecated(forRemoval = true)`.
Nothing implements it — the layered cover-letter presets implement the generic
`DocumentTemplate<CoverLetterDocumentSpec>` seam instead. Removed in 2.0.
- **`cv.v2.components.HeadlineRenderer` / `ContactRenderer` / `BannerRenderer`**
(already-deprecated pre-widgets shims) are now `forRemoval` — use the
`cv.v2.widgets` `Headline` / `ContactLine` / `SectionHeader` widgets instead.
Removed in 2.0.

### Documentation

- New runnable flagship example
Expand Down
8 changes: 4 additions & 4 deletions docs/adr/0011-templates-v2-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,10 +206,10 @@ references. The reopen made an explicit trade-off:
switch to the new factory (see migration table in `CHANGELOG.md`
and `docs/roadmaps/migration-v1-5-to-v1-6.md`).
- **Cinematic V2 templates remain.** `InvoiceTemplateV2` /
`ProposalTemplateV2` / `WeeklyScheduleTemplateV1` /
`BuiltInCvTemplateSupport` stay in `templates/builtins/`. The
builtins folder is not yet empty; final cleanup happens once
`ModernInvoice` / `ModernProposal` close cinematic feature parity.
`ProposalTemplateV2` / `WeeklyScheduleTemplateV1` stay in
`templates/builtins/`. The builtins folder is not yet empty; final
cleanup happens once `ModernInvoice` / `ModernProposal` close
cinematic feature parity.
- **`templates/data/` is partially deferred.**
`data/cv/CvDocumentSpec.java` and `data/coverletter/CoverLetterDocumentSpec.java`
are dead code referenced only by tests; `data/invoice/*` /
Expand Down
2 changes: 2 additions & 0 deletions docs/api-stability.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@ window starts, and its `Status` flips to `deprecated 1.x`.
| Element | Tier now | Status | Why the 1.x shape is a compromise | 2.0 action | ADR | Issue |
|---|---|---|---|---|---|---|
| `DocumentSession.pageMargins(List<PageMarginRule>)` / `PageMarginRule` | Stable | planned | Per-page margins resolve a block's content width by the page it *begins* on (the engine measures each block once, before pagination). A margin that changes the content width therefore does not re-wrap a block mid-flow across a page boundary. | Revisit a page-aware per-line/per-fragment width model so a block can re-wrap when it crosses a margin boundary, if demand warrants. | — | — |
| `templates.api.CoverLetterTemplate` | Stable | deprecated 1.9 | Orphan interface — nothing implements it; cover-letter presets implement the generic `DocumentTemplate<CoverLetterDocumentSpec>` seam. | Remove; callers implement `DocumentTemplate<CoverLetterDocumentSpec>`. | — | — |
| `templates.cv.v2.components.HeadlineRenderer` / `ContactRenderer` / `BannerRenderer` | Stable | deprecated 1.9 | Pre-widgets delegating shims superseded by the `cv.v2.widgets` `Headline` / `ContactLine` / `SectionHeader` widgets; no callers. | Remove; use the widgets. | — | — |

---

Expand Down
1 change: 0 additions & 1 deletion docs/architecture/package-map.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ intended.
| `com.demcha.compose.document.templates.blocks` | Templates-v2 module-body block kinds — `ParagraphBlock`, `BulletListBlock`, `NumberedListBlock`, `IndentedBlock`, `KeyValueBlock`, `MultiParagraphBlock`, `EducationBlock`, `WorkHistoryBlock`. | A block declares *what* content appears; the renderer expands it per active theme / tokens. |
| `com.demcha.compose.document.templates.decorations` | Templates-v2 decoration library — `Divider`, `AccentStrip`, `Spacer` (Panel / Banner / Ornament reserved). | First-class artefacts any preset can attach; not baked into composer logic. |
| `com.demcha.compose.document.templates.widgets` | Shared visual widgets usable by every family — `CardWidget`, `TableWidget`, `TimelineAxisWidget`. | Keep generic (no CV-only assumptions) so invoice / proposal / cover-letter can reuse them. |
| `com.demcha.compose.document.templates.tables` | **Reserved** for Templates-v2 reusable table styles (zebra / minimal-grid / borderless / accent-header / financial). Currently `package-info.java` only — no classes yet (Phase F). | Land table *styles* here, independent of the data they render. |

> **Preset families.** Concrete document families live under `…templates.<family>` — `cv`, `coverletter`, `invoice`, `proposal`, `schedule`. CV and cover letter additionally ship a layered v2 surface (`…cv.v2.*` / `…coverletter.v2.*`: `data` / `theme` / `components` / `widgets` / `presets`). These per-family packages are documented by the template guides rather than enumerated here — see [which-template-system.md](../templates/which-template-system.md) for the status matrix and [templates/v2-layered/](../templates/v2-layered/README.md) for the layered architecture.

Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,6 @@
package com.demcha.examples.support;

import com.demcha.compose.document.templates.blocks.BulletListBlock;
import com.demcha.compose.document.templates.blocks.EducationBlock;
import com.demcha.compose.document.templates.blocks.IndentedBlock;
import com.demcha.compose.document.templates.blocks.KeyValueBlock;
import com.demcha.compose.document.templates.blocks.MultiParagraphBlock;
import com.demcha.compose.document.templates.blocks.ParagraphBlock;
import com.demcha.compose.document.templates.blocks.WorkHistoryBlock;
import com.demcha.compose.document.templates.coverletter.spec.CoverLetterHeader;
import com.demcha.compose.document.templates.coverletter.spec.CoverLetterSpec;
import com.demcha.compose.document.templates.coverletter.v2.data.CoverLetterDocument;
import com.demcha.compose.document.templates.cv.spec.CvHeader;
import com.demcha.compose.document.templates.cv.spec.CvModule;
import com.demcha.compose.document.templates.cv.spec.CvSpec;
import com.demcha.compose.document.templates.cv.v2.data.CvDocument;
import com.demcha.compose.document.templates.cv.v2.data.CvIdentity;
import com.demcha.compose.document.templates.cv.v2.data.CvSkill;
Expand Down Expand Up @@ -262,195 +250,6 @@ private static ScheduleSlot slot(String start, String end) {
return ScheduleSlot.of(start, end);
}

// -- Templates v2 sample data ---------------------------------------

/**
* Returns a sample {@code CvSpec} for the Templates v2 CV pipeline.
* Mirrors the content of {@link #sampleCv()} in the v2 spec shape
* (immutable records, semantic Block kinds, markdown-aware
* paragraph text).
*
* @return sample v2 CV spec
*/
public static CvSpec sampleCvSpecV2() {
return CvSpec.builder()
.header(sampleCvHeaderV2())
.module(CvModule.of("Professional Summary",
new ParagraphBlock(
"Platform engineer with **10+ years** building resilient "
+ "document-generation pipelines, layout engines, and "
+ "developer-facing template systems. Specialised in "
+ "high-throughput PDF rendering, semantic authoring "
+ "DSLs, and turning brittle production-ops scripts "
+ "into typed, snapshot-tested libraries that scale.")))
.module(CvModule.of("Technical Skills",
new BulletListBlock(List.of(
"**Languages:** Java 21, Kotlin, Groovy, Python, SQL",
"**Document & Print:** PDFBox, Apache POI (DOCX/XLSX), iText, "
+ "PostScript, ICC colour profiles, font metrics",
"**Layout engines:** Custom DSL design, semantic layout trees, "
+ "pagination, snapshot testing, visual regression",
"**Build & infrastructure:** Maven, Gradle, GitHub Actions, "
+ "JitPack, Docker, JMH benchmarking",
"**Testing:** JUnit 5, AssertJ, PDFBox-based PNG diff, "
+ "layout-graph snapshots, mutation testing (Pitest)",
"**Distribution:** Maven Central, Sonatype OSSRH, GPG signing, "
+ "JitPack, semantic versioning discipline"))))
.module(CvModule.of("Education & Certifications",
// Preferred: structured EducationBlock with
// explicit (degree, institution, year, details)
// fields. BoxedSections renders each item with
// the same structured layout as Professional
// Experience — degree bold left, year right,
// institution italic on the next line, and
// details as a full-width paragraph below.
new EducationBlock(List.of(
new EducationBlock.Item(
"MSc Computer Science",
"University of Manchester",
"2021",
"Distinction. Thesis: *Composable layout primitives "
+ "for deterministic document rendering*."),
new EducationBlock.Item(
"BSc Software Engineering",
"Imperial College London",
"2019",
"First-class honours. Specialisation in compilers and "
+ "static analysis."),
new EducationBlock.Item(
"Oracle Java Certification",
"Professional track",
"2023",
"Java 17 platform deep-dive: records, sealed types, "
+ "pattern matching, virtual threads.")))))
.module(CvModule.of("Projects",
new BulletListBlock(List.of(
"**GraphCompose (Java 21, PDFBox, Maven, JMH)** - "
+ "Declarative Java PDF layout engine. Semantic DSL, "
+ "slot-based templates, snapshot testing. Powers "
+ "production CV / invoice / proposal pipelines for "
+ "hiring tools and billing systems. *(Open source)*",
"**Template Studio (Kotlin, Compose Desktop, PDFBox PNG diff)** - "
+ "Internal tool for evaluating CV, proposal, and "
+ "invoice output across 14 design presets. PNG "
+ "diffing, side-by-side layout, baseline freezing.",
"**LayoutLint (Java 21, JavaParser, Spoon)** - Static analyser "
+ "that flags fragile authoring patterns (deeply "
+ "nested rows, untyped offsets, implicit page "
+ "breaks) before they ship to production.",
"**ChromeForge (Java, GraphCompose, Pandoc bridge)** - "
+ "Editorial-magazine document toolkit built on "
+ "GraphCompose: cinematic covers, pull quotes, "
+ "multi-column flow, sidebar callouts."))))
.module(CvModule.of("Professional Experience",
// Preferred: structured WorkHistoryBlock with
// explicit (title, organisation, date,
// description) fields. BoxedSections renders
// each item as a structured row (title bold
// left, date right, organisation italic on the
// next line, description full-width below)
// without falling back to the legacy
// pipe-separated string parser.
new WorkHistoryBlock(List.of(
new WorkHistoryBlock.Item(
"Senior Platform Engineer",
"Northwind Systems",
"2024-Present",
"Led the reusable document-generation platform serving "
+ "billing, hiring, and reporting flows across "
+ "**8 product teams**. Reduced template maintenance "
+ "time by **70%** by retiring per-team PDF scripts "
+ "in favour of one canonical engine."),
new WorkHistoryBlock.Item(
"Software Engineer",
"BrightLeaf Labs",
"2021-2024",
"Built backend services and production document rendering "
+ "pipelines processing **2M+ documents per month**. "
+ "Drove the migration from iText to a custom layout "
+ "engine, eliminating licensing risk and cutting "
+ "p99 render latency from 1.4s to 380ms."),
new WorkHistoryBlock.Item(
"Backend Engineer",
"Helix Print Co",
"2019-2021",
"Maintained a high-volume invoice-printing service "
+ "(15M PDFs/year) and authored the compliance test "
+ "harness that gated every template change.")))))
.module(CvModule.of("Additional Information",
new KeyValueBlock(List.of(
new KeyValueBlock.Entry("Languages",
"English (Fluent), German (Intermediate), Spanish (Basic)"),
new KeyValueBlock.Entry("Work Eligibility",
"Eligible to work in the UK and the EU"),
new KeyValueBlock.Entry("Open Source",
"Maintainer of GraphCompose. Regular contributor to PDFBox issue triage."),
new KeyValueBlock.Entry("Speaking",
"JVM Summit 2024, Devoxx UK 2025 — both on declarative document layout.")))))
.build();
}

/**
* Returns a sample {@code CvHeader} for the Templates v2 CV
* pipeline.
*
* @return sample v2 CV header
*/
public static CvHeader sampleCvHeaderV2() {
return CvHeader.builder()
.name("Jordan Rivera")
.jobTitle("Backend Java Developer")
.address("London, UK")
.phone("+44 20 5555 1000")
.email("jordan.rivera@example.com")
.link("LinkedIn", "https://linkedin.com/in/jordan-rivera-demo")
.link("GitHub", "https://github.com/jrivera-demo")
.build();
}

/**
* Returns a sample {@code CoverLetterSpec} for the Templates v2
* cover-letter pipeline.
*
* @return sample v2 cover letter spec
*/
public static CoverLetterSpec sampleCoverLetterSpecV2() {
return CoverLetterSpec.builder()
.header(sampleCoverLetterHeaderV2())
.greeting("Dear Hiring Team at **Northwind Systems**,")
.paragraph("I am excited to share my interest in the Senior "
+ "Platform Engineer role. My recent work has focused "
+ "on building **reusable document-generation systems** "
+ "that balance public API design, render quality, and "
+ "maintainability.")
.paragraph("I enjoy translating fuzzy workflow requirements into "
+ "clear template abstractions, reliable test coverage, "
+ "and examples that make adoption easier for the rest "
+ "of the team.")
.paragraph("I would welcome the opportunity to bring that same "
+ "mix of engineering rigor and product thinking to your "
+ "platform group.")
.closing("Sincerely, *Artem Demchyshyn*")
.build();
}

/**
* Returns a sample {@code CoverLetterHeader} mirroring the v2 CV
* header so a writer's CV and cover letter ship as a matched set.
*
* @return sample v2 cover letter header
*/
public static CoverLetterHeader sampleCoverLetterHeaderV2() {
return CoverLetterHeader.builder()
.name("Jordan Rivera")
.address("London, UK")
.phone("+44 20 5555 1000")
.email("jordan.rivera@example.com")
.link("LinkedIn", "https://linkedin.com/in/jordan-rivera-demo")
.link("GitHub", "https://github.com/jrivera-demo")
.build();
}

// -- Templates v2 (cv/v2) sample data ------------------------------

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,13 @@
* semantic blocks into a caller-owned {@link DocumentSession}. Implementations
* are usually immutable value objects configured by a theme.</p>
*
* <pre>{@code
* CoverLetterTemplate template = new CoverLetterTemplateV1();
* CoverLetterDocumentSpec coverLetter = CoverLetterDocumentSpec.builder()
* .header(header -> header
* .name("Artem Demchyshyn")
* .email("artem@demo.dev", "artem@demo.dev")
* .linkedIn("https://linkedin.com/in/graphcompose", "LinkedIn"))
* .letter(letterBody)
* .job(job -> job
* .company("Northwind Systems")
* .title("Platform Engineer"))
* .build();
*
* try (DocumentSession document = GraphCompose.document(Path.of("cover-letter.pdf")).create()) {
* template.compose(document, coverLetter);
* document.buildPdf();
* }
* }</pre>
* @deprecated since 1.9.0; removed in 2.0. No type implements this interface —
* the layered cover-letter presets implement the generic
* {@link DocumentTemplate} seam
* ({@code DocumentTemplate<CoverLetterDocumentSpec>}) instead.
* Implement {@code DocumentTemplate<CoverLetterDocumentSpec>}.
*/
@Deprecated(since = "1.9.0", forRemoval = true)
public interface CoverLetterTemplate {

/**
Expand Down
Loading
Loading