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: 5 additions & 5 deletions docs/templates/v2-layered/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ experience, your skills.

→ **[using-templates.md](using-templates.md)**

You'll learn the `CvDocument` builder API, the three section types
(paragraph / rows / entries), how slots place sections into columns,
and how to swap a theme (colours / fonts / glyphs) without forking a
preset.
You'll learn the `CvDocument` builder API, the built-in section types
(paragraph / grouped skills / rows / entries), how slots place
sections into columns, and how to swap a theme (colours / fonts /
glyphs) without forking a preset.

### 🎨 You want a custom visual style on top of v2
Existing presets aren't quite your design. You want a new look —
Expand Down Expand Up @@ -121,7 +121,7 @@ The detailed contract for each layer is in
glyph" to "add a new section subtype".
- **Examples**:
[`examples/cv/v2/`](../../examples/src/main/java/com/demcha/examples/templates/cv/v2)
has three runnable rendering examples — one per shipped preset.
has runnable rendering examples for the shipped presets.
- **Legacy v1 surface**:
[`docs/templates/v1-classic/README.md`](../v1-classic/README.md) describes the older
spec / preset / theme split used by the v1 templates. Still valid
Expand Down
7 changes: 4 additions & 3 deletions docs/templates/v2-layered/authoring-presets.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

You like the layered architecture, but the shipped presets
(`BoxedSections`, `MinimalUnderlined`, `ModernProfessional`,
`CenteredHeadline`, `BlueBanner`) don't match the design you want.
This doc walks you through writing a new preset from scratch —
**without subclassing, without duplicating rendering code**.
`CenteredHeadline`, `BlueBanner`, `EditorialBlue`) don't match the
design you want. This doc walks you through writing a new preset from
scratch — **without subclassing, without duplicating rendering code**.

If you haven't read [quickstart.md](quickstart.md) and
[using-templates.md](using-templates.md), do those first.
Expand Down Expand Up @@ -68,6 +68,7 @@ small set of named variants.
| Variant | Visual |
|---|---|
| `Headline.spacedCentered(host, name, theme)` | Centred letter-spaced uppercase (`J A N E D O E`) |
| `Headline.uppercaseCentered(host, name, theme)` | Centred uppercase without extra spacing (`JANE DOE`) |
| `Headline.rightAligned(host, name, theme)` | Right-aligned plain bold (`Jane Doe`) |
| `Headline.render(host, name, theme, align, spacedCaps)` | Low-level — any (alignment, transform) combo |

Expand Down
15 changes: 8 additions & 7 deletions docs/templates/v2-layered/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ GraphCompose's templates v2 (layered) gives you:
you can drop into a preset.
- **Presets as compositions** — a preset orchestrates widgets in a
page flow. `BoxedSections`, `MinimalUnderlined`,
`ModernProfessional`, `CenteredHeadline`, `BlueBanner` ship today;
writing your own is ~150 lines.
`ModernProfessional`, `CenteredHeadline`, `BlueBanner`, and
`EditorialBlue` ship today; writing your own is ~150 lines.

You hand a `CvDocument` to a preset, you get a PDF. The preset
internally composes widgets that read theme tokens that ultimately
Expand Down Expand Up @@ -49,9 +49,9 @@ CvDocument doc = CvDocument.builder()
.section(new ParagraphSection("Professional Summary",
"Backend engineer with **5 years** of experience building "
+ "high-throughput payment systems."))
.section(RowsSection.builder("Technical Skills", RowStyle.BULLETED)
.row("Languages", "Java 21, Kotlin, SQL")
.row("Frameworks", "Spring Boot, Quarkus")
.section(SkillsSection.builder("Technical Skills")
.group("Languages", "Java 21", "Kotlin", "SQL")
.group("Frameworks", "Spring Boot", "Quarkus")
.build())
.section(EntriesSection.builder("Experience")
.entry("Senior Engineer", "Acme Payments", "2022-Present",
Expand Down Expand Up @@ -89,7 +89,7 @@ Same data, different visual. That's the layering.
┌─────────────────────────────────────────────────────────────┐
│ presets/ BoxedSections, MinimalUnderlined, │
│ ModernProfessional, CenteredHeadline, │
│ BlueBanner
│ BlueBanner, EditorialBlue
│ — composition of widgets in a page flow │
└─────────────────────────────────────────────────────────────┘
│ compose from widgets
Expand All @@ -113,7 +113,8 @@ Same data, different visual. That's the layering.
┌─────────────────────────────────────────────────────────────┐
│ data/ CvDocument, CvIdentity, CvSection (sealed), │
│ ParagraphSection / RowsSection / EntriesSection,│
│ ParagraphSection / SkillsSection / RowsSection │
│ / EntriesSection, │
│ CvRow, CvEntry, Slot │
│ — pure records, zero rendering deps │
└─────────────────────────────────────────────────────────────┘
Expand Down
54 changes: 35 additions & 19 deletions docs/templates/v2-layered/using-templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ it sets up the conceptual model in 5 minutes.

## Table of contents

1. [The three pieces you assemble](#the-three-pieces-you-assemble)
1. [The pieces you assemble](#the-pieces-you-assemble)
2. [Identity — name, contact, optional links](#identity)
3. [Section types — three shapes cover every case](#section-types)
3. [Section types](#section-types)
4. [Slots — main vs sidebar](#slots)
5. [Picking a preset](#picking-a-preset)
6. [Customising a theme](#customising-a-theme)
Expand All @@ -23,7 +23,7 @@ it sets up the conceptual model in 5 minutes.

---

## The three pieces you assemble
## The Pieces You Assemble

```java
CvDocument doc = …; // your content
Expand Down Expand Up @@ -76,9 +76,9 @@ The widget renders the label; the URL is the click target.
---

<a id="section-types"></a>
## Section types — three shapes cover every case
## Section Types

The `CvSection` sealed hierarchy has exactly **three** concrete
The `CvSection` sealed hierarchy has a small set of concrete
shapes. Each captures a structurally different content pattern, not
a visual flavour.

Expand All @@ -93,15 +93,31 @@ new ParagraphSection("Professional Summary",

Inline markdown (`**bold**`, `*italic*`, `_italic_`) is honoured.

### 2. `RowsSection` — list of label/body rows with a decoration style
### 2. `SkillsSection` — grouped skills

For Technical Skills, Languages, Awards, Additional Information,
Projects, anything with "label: value" entries.
For Technical Skills and similar capability groups where the content
is naturally `category -> skills[]`.

```java
RowsSection.builder("Technical Skills", RowStyle.BULLETED)
.row("Languages", "Java 21, Kotlin, SQL")
.row("Tools", "Maven, Docker, GitHub Actions")
SkillsSection.builder("Technical Skills")
.group("Languages", "Java 21", "Kotlin", "SQL")
.group("Tools", "Maven", "Docker", "GitHub Actions")
.build();
```

Keeping skills grouped means the same CV data can render as bullets,
a grid/table, sidebar chips, or compact inline rows depending on the
preset.

### 3. `RowsSection` — list of label/body rows with a decoration style

For Languages, Awards, Additional Information, Projects, anything
with "label: value" entries that is not a skill taxonomy.

```java
RowsSection.builder("Additional Information", RowStyle.PLAIN)
.row("Languages", "English (Fluent), German (Intermediate)")
.row("Work Eligibility", "Eligible to work in the UK and the EU")
.build();
```

Expand All @@ -114,11 +130,10 @@ RowStyle.BULLETED_STACKED // • <b>Label</b>
// body (on second line, indented)
```

The same `RowsSection` type covers Technical Skills, Additional
Information, Projects — pick the style that matches the visual
density you want.
The same `RowsSection` type covers Additional Information and
Projects — pick the style that matches the visual density you want.

### 3. `EntriesSection` — timeline entries (title / subtitle / date / body)
### 4. `EntriesSection` — timeline entries (title / subtitle / date / body)

For Education, Professional Experience — anything where you have a
list of items each with a title, subtitle, date, and description.
Expand Down Expand Up @@ -159,9 +174,9 @@ CvDocument doc = CvDocument.builder()
```

**Single-column presets** (`BoxedSections`, `MinimalUnderlined`,
`ModernProfessional`, `CenteredHeadline`, `BlueBanner`) render only
`Slot.MAIN`. Sidebar content is silently dropped — switch to a
multi-column preset to render it.
`ModernProfessional`, `CenteredHeadline`, `BlueBanner`,
`EditorialBlue`) render only `Slot.MAIN`. Sidebar content is silently
dropped — switch to a multi-column preset to render it.

If you don't use slots at all, your sections go to `MAIN` and every
preset renders them. The slot model is opt-in.
Expand All @@ -171,7 +186,7 @@ preset renders them. The slot model is opt-in.
<a id="picking-a-preset"></a>
## Picking a preset

Five shipped today:
Six shipped today:

| Preset | Visual signature |
|---|---|
Expand All @@ -180,6 +195,7 @@ Five shipped today:
| `ModernProfessional.create()` | Right-aligned big slate-blue name, flat bright-blue bold section titles, dense single page |
| `CenteredHeadline.create()` | Centred spaced-caps name, small subheadline, full-width rules around contact and modules |
| `BlueBanner.create()` | Centred PT-Serif name, compact Lato body, blue full-width section banners between thin rules |
| `EditorialBlue.create()` | Centred uppercase masthead, optional job-title subtitle, blue editorial rules, compact skills table |

Each factory has a no-arg form (uses a sensible default theme) and
a `create(CvTheme)` form (custom theme).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import com.demcha.compose.document.templates.cv.v2.data.ParagraphSection;
import com.demcha.compose.document.templates.cv.v2.data.RowStyle;
import com.demcha.compose.document.templates.cv.v2.data.RowsSection;
import com.demcha.compose.document.templates.cv.v2.data.SkillsSection;
import com.demcha.compose.document.templates.data.common.EmailYaml;
import com.demcha.compose.document.templates.data.common.Header;
import com.demcha.compose.document.templates.data.coverletter.CoverLetterDocumentSpec;
Expand Down Expand Up @@ -452,14 +453,16 @@ public static CoverLetterHeader sampleCoverLetterHeaderV2() {

/**
* Returns a sample {@code CvDocument} for the v2 CV pipeline —
* the canonical Jordan Rivera content expressed in the new
* 3-section sealed hierarchy
* the canonical Jordan Rivera content expressed in the v2
* sealed section hierarchy
* ({@link com.demcha.compose.document.templates.cv.v2.data.ParagraphSection},
* {@link SkillsSection},
* {@link RowsSection},
* {@link EntriesSection}).
*
* <p>Each section explicitly picks its visual decoration —
* Technical Skills uses {@link RowStyle#BULLETED}, Projects uses
* <p>Technical Skills is grouped semantically via
* {@link SkillsSection}; row-style sections still explicitly pick
* their visual decoration. Projects uses
* {@link RowStyle#BULLETED_STACKED}, Additional Information uses
* {@link RowStyle#PLAIN}. Education and Experience are the same
* {@link EntriesSection} record, distinguished only by title.</p>
Expand All @@ -469,6 +472,7 @@ public static CoverLetterHeader sampleCoverLetterHeaderV2() {
public static CvDocument sampleCvDocumentV2() {
CvIdentity identity = CvIdentity.builder()
.name("Jordan", "Rivera")
.jobTitle("Platform Engineer")
.contact("+44 20 5555 1000",
"jordan.rivera@example.com",
"London, UK")
Expand All @@ -485,31 +489,37 @@ public static CvDocument sampleCvDocumentV2() {
+ "DSLs, and turning brittle production-ops scripts "
+ "into typed, snapshot-tested libraries that scale.");

RowsSection skills = RowsSection
.builder("Technical Skills", RowStyle.BULLETED)
.row("Languages", "Java 21, Kotlin, Groovy, Python, SQL")
.row("Document & Print", "PDFBox, Apache POI (DOCX/XLSX), iText, "
+ "PostScript, ICC colour profiles, font metrics")
.row("Layout engines", "Custom DSL design, semantic layout trees, "
+ "pagination, snapshot testing, visual regression")
.row("Build & infrastructure", "Maven, Gradle, GitHub Actions, "
+ "JitPack, Docker, JMH benchmarking")
.row("Testing", "JUnit 5, AssertJ, PDFBox-based PNG diff, "
+ "layout-graph snapshots, mutation testing (Pitest)")
.row("Distribution", "Maven Central, Sonatype OSSRH, GPG signing, "
+ "JitPack, semantic versioning discipline")
SkillsSection skills = SkillsSection
.builder("Technical Skills")
.group("Languages", "Java 21", "Kotlin", "Groovy",
"Python", "SQL")
.group("Document & Print", "PDFBox",
"Apache POI (DOCX/XLSX)", "iText", "PostScript",
"ICC colour profiles", "font metrics")
.group("Layout engines", "Custom DSL design",
"semantic layout trees", "pagination",
"snapshot testing", "visual regression")
.group("Build & infrastructure", "Maven", "Gradle",
"GitHub Actions", "JitPack", "Docker",
"JMH benchmarking")
.group("Testing", "JUnit 5", "AssertJ",
"PDFBox-based PNG diff", "layout-graph snapshots",
"mutation testing (Pitest)")
.group("Distribution", "Maven Central", "Sonatype OSSRH",
"GPG signing", "JitPack",
"semantic versioning discipline")
.build();

EntriesSection education = EntriesSection
.builder("Education & Certifications")
.entry("MSc Computer Science",
"University of Manchester",
"2020-2021",
"2019-2021",
"Distinction. Thesis: *Composable layout primitives "
+ "for deterministic document rendering*.")
.entry("BSc Software Engineering",
"Imperial College London",
"2016-2019",
"2015-2019",
"First-class honours. Specialisation in compilers and "
+ "static analysis.")
.entry("Oracle Java Certification",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.demcha.examples.templates.cv.v2;

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.api.DocumentTemplate;
import com.demcha.compose.document.templates.cv.v2.data.CvDocument;
import com.demcha.compose.document.templates.cv.v2.presets.EditorialBlue;
import com.demcha.examples.support.ExampleDataFactory;
import com.demcha.examples.support.ExampleOutputPaths;

import java.nio.file.Path;

/**
* Renders the v2 Editorial Blue CV preset against the shared grouped
* skills sample data — centred uppercase masthead, optional job-title
* subtitle, blue editorial rules, and compact skills table.
*
* <p>Output:
* {@code examples/target/generated-pdfs/templates/cv/cv-editorial-blue-v2.pdf}.</p>
*/
public final class CvEditorialBlueExample {

private CvEditorialBlueExample() {
}

public static Path generate() throws Exception {
Path outputFile = ExampleOutputPaths.prepare(
"templates/cv", "cv-editorial-blue-v2.pdf");
CvDocument doc = ExampleDataFactory.sampleCvDocumentV2();
DocumentTemplate<CvDocument> template = EditorialBlue.create();

float m = (float) EditorialBlue.RECOMMENDED_MARGIN;
try (DocumentSession document = GraphCompose.document(outputFile)
.pageSize(DocumentPageSize.A4)
.margin(m, m, m, m)
.create()) {
template.compose(document, doc);
document.buildPdf();
}
return outputFile;
}

public static void main(String[] args) throws Exception {
System.out.println("Generated: " + generate());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,10 @@ CvDocument.builder()
Notice what's absent:

- **No `link(...)` calls.** Optional, simply omitted.
- **No Projects, no Skills.** The three section types
(`ParagraphSection`, `RowsSection`, `EntriesSection`) work for any
content shape — you choose what to put in them and what to call
them.
- **No Projects, no Skills.** The built-in section types
(`ParagraphSection`, `RowsSection`, `EntriesSection`, `SkillsSection`)
work for common CV shapes — you choose what to put in them and what
to call them.
- **No required IT vocabulary anywhere.** Section titles are free
strings (`"About Me"`, `"Teaching Experience"`, `"Certifications"`).

Expand Down Expand Up @@ -210,7 +210,7 @@ calls them. No inheritance, no instance state to manage.

## Recipe 5 — add a brand-new section subtype

You need something the existing three section types can't express —
You need something the existing section types can't express —
say, a skill-bar chart, a quote block, or a contact-references list.

Three places to touch (compile-checked path):
Expand All @@ -233,7 +233,8 @@ public record QuoteSection(String title, String quote, String attribution)

```java
public sealed interface CvSection
permits ParagraphSection, RowsSection, EntriesSection, QuoteSection {
permits ParagraphSection, RowsSection, EntriesSection,
SkillsSection, QuoteSection {
String title();
}
```
Expand Down Expand Up @@ -325,6 +326,7 @@ as DSL plumbing. Below is the current catalog.
| Variant | Visual | Used in |
|---|---|---|
| `Headline.spacedCentered(host, name, theme)` | centred letter-spaced uppercase (`J A N E D O E`) | BoxedSections, MinimalUnderlined, CenteredHeadline, BlueBanner |
| `Headline.uppercaseCentered(host, name, theme)` | centred uppercase without extra spacing (`JANE DOE`) | EditorialBlue |
| `Headline.rightAligned(host, name, theme)` | right-aligned plain bold (`Jane Doe`) | ModernProfessional |
| `Headline.render(host, name, theme, align, spacedCaps)` | low-level: pick any alignment + transform | — |

Expand Down
Loading