From dcbe82b82464e0ee759120f85cc479c8f9690b37 Mon Sep 17 00:00:00 2001
From: DemchaAV
Date: Sun, 24 May 2026 23:48:31 +0200
Subject: [PATCH 1/2] Add CenteredHeadline CV v2 preset
---
docs/templates/v2-layered/README.md | 2 +-
.../templates/v2-layered/authoring-presets.md | 29 ++-
docs/templates/v2-layered/quickstart.md | 16 +-
docs/templates/v2-layered/using-templates.md | 8 +-
.../examples/support/ExampleDataFactory.java | 6 +-
.../document/templates/cv/v2/AUTHORS.md | 20 +-
.../templates/cv/v2/package-info.java | 13 +-
.../cv/v2/presets/CenteredHeadline.java | 234 ++++++++++++++++++
.../templates/cv/v2/theme/CvPalette.java | 20 ++
.../templates/cv/v2/theme/CvSpacing.java | 26 ++
.../templates/cv/v2/theme/CvTheme.java | 15 ++
.../templates/cv/v2/theme/CvTypography.java | 24 ++
.../cv/v2/widgets/SectionHeader.java | 46 ++++
.../templates/cv/v2/widgets/Subheadline.java | 66 +++++
.../templates/cv/v2/widgets/package-info.java | 14 +-
.../cv/v2/presets/CvV2VisualParityTest.java | 11 +-
.../cv/v2/widgets/WidgetSmokeTest.java | 11 +
.../cv-v2-layered/boxed_sections-page-0.png | Bin 100039 -> 100687 bytes
.../centered_headline-page-0.png | Bin 0 -> 110502 bytes
.../centered_headline-page-1.png | Bin 0 -> 21703 bytes
.../minimal_underlined-page-0.png | Bin 98515 -> 99160 bytes
.../modern_professional-page-0.png | Bin 148989 -> 149826 bytes
22 files changed, 518 insertions(+), 43 deletions(-)
create mode 100644 src/main/java/com/demcha/compose/document/templates/cv/v2/presets/CenteredHeadline.java
create mode 100644 src/main/java/com/demcha/compose/document/templates/cv/v2/widgets/Subheadline.java
create mode 100644 src/test/resources/visual-baselines/cv-v2-layered/centered_headline-page-0.png
create mode 100644 src/test/resources/visual-baselines/cv-v2-layered/centered_headline-page-1.png
diff --git a/docs/templates/v2-layered/README.md b/docs/templates/v2-layered/README.md
index d9f484f4..765e91cc 100644
--- a/docs/templates/v2-layered/README.md
+++ b/docs/templates/v2-layered/README.md
@@ -71,7 +71,7 @@ template family from empty folder to merged PR.
presets/ composition: data + theme + widgets → DocumentTemplate
│ compose from
▼
-widgets/ LEGO bricks: Headline, ContactLine, SectionHeader, …
+widgets/ LEGO bricks: Headline, Subheadline, ContactLine, SectionHeader, …
│ delegate to read tokens from
▼ ▼
components/ internal renderers + primitives theme/ palette
diff --git a/docs/templates/v2-layered/authoring-presets.md b/docs/templates/v2-layered/authoring-presets.md
index 385e7328..387c44a0 100644
--- a/docs/templates/v2-layered/authoring-presets.md
+++ b/docs/templates/v2-layered/authoring-presets.md
@@ -1,10 +1,10 @@
# Authoring Presets — write your own visual style
-You like the layered architecture, but the three shipped presets
-(`BoxedSections`, `MinimalUnderlined`, `ModernProfessional`) don't
-match the design you want. This doc walks you through writing a
-new preset from scratch — **without subclassing, without duplicating
-rendering code**.
+You like the layered architecture, but the shipped presets
+(`BoxedSections`, `MinimalUnderlined`, `ModernProfessional`,
+`CenteredHeadline`) 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.
@@ -59,9 +59,9 @@ visual decision you can read like a recipe.
## The widget catalog
-Today, three widget classes live in
-`com.demcha.compose.document.templates.cv.v2.widgets`. Each has 2-3
-named variants.
+Today, four widget classes live in
+`com.demcha.compose.document.templates.cv.v2.widgets`. Each has a
+small set of named variants.
### `Headline` — top-of-document name
@@ -71,12 +71,19 @@ named variants.
| `Headline.rightAligned(host, name, theme)` | Right-aligned plain bold (`Jane Doe`) |
| `Headline.render(host, name, theme, align, spacedCaps)` | Low-level — any (alignment, transform) combo |
+### `Subheadline` — secondary tagline under the name
+
+| Variant | Visual |
+|---|---|
+| `Subheadline.centeredSpacedCaps(host, text, style)` | Centred letter-spaced uppercase tagline (`P R O F E S S I O N A L T I T L E`) |
+
### `ContactLine` — phone / email / address / links row
| Variant | Visual |
|---|---|
| `ContactLine.centered(host, identity, theme)` | Centred, phone → email → address → links |
| `ContactLine.rightAligned(host, identity, theme)` | Right-aligned, address → phone → email → links |
+| `ContactLine.twoRowRightAligned(host, identity, theme, bodyStyle, linkStyle, separatorStyle)` | Right-aligned address/phone row plus email/link row |
| `ContactLine.render(host, identity, theme, align, order)` | Low-level — any alignment + field-order combo |
### `SectionHeader` — title above each section body
@@ -86,6 +93,7 @@ named variants.
| `SectionHeader.banner(host, title, theme)` | Pale-grey panel + centred spaced-caps inside |
| `SectionHeader.underlined(host, title, theme)` | Small left spaced-caps + thin rule below |
| `SectionHeader.flat(host, title, color, theme)` | Large bold title in a given colour, no panel |
+| `SectionHeader.flatSpacedCaps(host, title, color, theme, titleStyle)` | Small left spaced-caps title in a soft colour, no panel |
The separator glyph used by `ContactLine`, the bullet glyph used by
`RowRenderer`, and other character-level choices come from
@@ -303,9 +311,10 @@ When you do add a new widget:
1. **One file per widget** in `cv/v2/widgets/`.
2. **`public final class`** with a private constructor.
-3. **2-3 named factories** + a lower-level `.render(...)`.
+3. **1-3 named factories** + a lower-level `.render(...)` when useful.
4. **First parameter is always `SectionBuilder host`**.
-5. **Last parameter is always `CvTheme theme`**.
+5. **Pass `CvTheme theme` when the widget reads shared tokens**;
+ pass an explicit style only when the preset owns that unique style.
6. **No instance state** — all static, all stateless.
7. **JavaDoc the visual** — what does this look like? Who uses it?
8. **Add to `WidgetSmokeTest`** with a basic "renders without
diff --git a/docs/templates/v2-layered/quickstart.md b/docs/templates/v2-layered/quickstart.md
index 8d7de664..2838b3f4 100644
--- a/docs/templates/v2-layered/quickstart.md
+++ b/docs/templates/v2-layered/quickstart.md
@@ -14,12 +14,13 @@ GraphCompose's templates v2 (layered) gives you:
- **Themes describing visuals** — `CvTheme` (palette + typography +
spacing + decoration). Swap a theme to change colours, fonts,
bullet glyphs without touching renderers.
-- **Widgets as visual LEGO bricks** — `Headline`, `ContactLine`,
- `SectionHeader`. Each one is a named visual decision you can drop
- into a preset.
+- **Widgets as visual LEGO bricks** — `Headline`, `Subheadline`,
+ `ContactLine`, `SectionHeader`. Each one is a named visual decision
+ you can drop into a preset.
- **Presets as compositions** — a preset orchestrates widgets in a
- page flow. `BoxedSections`, `MinimalUnderlined`, `ModernProfessional`
- ship today; writing your own is ~150 lines.
+ page flow. `BoxedSections`, `MinimalUnderlined`,
+ `ModernProfessional`, `CenteredHeadline` 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
@@ -87,13 +88,14 @@ Same data, different visual. That's the layering.
```
┌─────────────────────────────────────────────────────────────┐
│ presets/ BoxedSections, MinimalUnderlined, │
-│ ModernProfessional │
+│ ModernProfessional, CenteredHeadline │
│ — composition of widgets in a page flow │
└─────────────────────────────────────────────────────────────┘
│ compose from widgets
▼
┌─────────────────────────────────────────────────────────────┐
-│ widgets/ Headline, ContactLine, SectionHeader │
+│ widgets/ Headline, Subheadline, ContactLine, │
+│ SectionHeader │
│ — named visual LEGO bricks │
└─────────────────────────────────────────────────────────────┘
│ delegate to ↓ │ read tokens from ↓
diff --git a/docs/templates/v2-layered/using-templates.md b/docs/templates/v2-layered/using-templates.md
index 94b499a9..e09eafb9 100644
--- a/docs/templates/v2-layered/using-templates.md
+++ b/docs/templates/v2-layered/using-templates.md
@@ -159,8 +159,9 @@ CvDocument doc = CvDocument.builder()
```
**Single-column presets** (`BoxedSections`, `MinimalUnderlined`,
-`ModernProfessional`) render only `Slot.MAIN`. Sidebar content is
-silently dropped — switch to a multi-column preset to render it.
+`ModernProfessional`, `CenteredHeadline`) 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.
@@ -170,13 +171,14 @@ preset renders them. The slot model is opt-in.
## Picking a preset
-Three shipped today:
+Four shipped today:
| Preset | Visual signature |
|---|---|
| `BoxedSections.create()` | Centred letter-spaced name, pale-grey panel section banners, two-page friendly |
| `MinimalUnderlined.create()` | Centred name with thin rule, small spaced-caps section titles with accent rule, single page |
| `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 |
Each factory has a no-arg form (uses a sensible default theme) and
a `create(CvTheme)` form (custom theme).
diff --git a/examples/src/main/java/com/demcha/examples/support/ExampleDataFactory.java b/examples/src/main/java/com/demcha/examples/support/ExampleDataFactory.java
index 9a0bac7f..a762b8c0 100644
--- a/examples/src/main/java/com/demcha/examples/support/ExampleDataFactory.java
+++ b/examples/src/main/java/com/demcha/examples/support/ExampleDataFactory.java
@@ -504,17 +504,17 @@ public static CvDocument sampleCvDocumentV2() {
.builder("Education & Certifications")
.entry("MSc Computer Science",
"University of Manchester",
- "2021",
+ "2020-2021",
"Distinction. Thesis: *Composable layout primitives "
+ "for deterministic document rendering*.")
.entry("BSc Software Engineering",
"Imperial College London",
- "2019",
+ "2016-2019",
"First-class honours. Specialisation in compilers and "
+ "static analysis.")
.entry("Oracle Java Certification",
"Professional track",
- "2023",
+ "2023-2024",
"Java 17 platform deep-dive: records, sealed types, "
+ "pattern matching, virtual threads.")
.build();
diff --git a/src/main/java/com/demcha/compose/document/templates/cv/v2/AUTHORS.md b/src/main/java/com/demcha/compose/document/templates/cv/v2/AUTHORS.md
index 979151bc..1ee92c73 100644
--- a/src/main/java/com/demcha/compose/document/templates/cv/v2/AUTHORS.md
+++ b/src/main/java/com/demcha/compose/document/templates/cv/v2/AUTHORS.md
@@ -324,16 +324,23 @@ 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 |
+| `Headline.spacedCentered(host, name, theme)` | centred letter-spaced uppercase (`J A N E D O E`) | BoxedSections, MinimalUnderlined, CenteredHeadline |
| `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 | — |
+### `Subheadline` — secondary tagline under the name
+
+| Variant | Visual | Used in |
+|---|---|---|
+| `Subheadline.centeredSpacedCaps(host, text, style)` | centred letter-spaced uppercase tagline | CenteredHeadline |
+
### `ContactLine` — phone / email / address / links row
| Variant | Visual | Used in |
|---|---|---|
-| `ContactLine.centered(host, identity, theme)` | centred, phone → email → address → links | BoxedSections, MinimalUnderlined |
+| `ContactLine.centered(host, identity, theme)` | centred, phone → email → address → links | BoxedSections, MinimalUnderlined, CenteredHeadline |
| `ContactLine.rightAligned(host, identity, theme)` | right-aligned, address → phone → email → links | ModernProfessional |
+| `ContactLine.twoRowRightAligned(host, identity, theme, bodyStyle, linkStyle, separatorStyle)` | right-aligned address/phone row plus email/link row | ModernProfessional |
| `ContactLine.render(host, identity, theme, align, order)` | low-level: pick alignment + field order | — |
The separator glyph comes from
@@ -347,11 +354,12 @@ change ` | ` to ` · ` or anything else.
| `SectionHeader.banner(host, title, theme)` | pale-grey panel with centred spaced-caps inside | BoxedSections |
| `SectionHeader.underlined(host, title, theme)` | small spaced-caps left-aligned, thin rule below | MinimalUnderlined |
| `SectionHeader.flat(host, title, color, theme)` | large bold title in a given colour, no panel | ModernProfessional |
+| `SectionHeader.flatSpacedCaps(host, title, color, theme, titleStyle)` | small spaced-caps title in a soft colour, no panel | CenteredHeadline |
-Note that `flat` takes a `DocumentColor` argument — the section
-title colour is the preset's signature accent, and the widget
-deliberately surfaces it as a parameter rather than burying it in
-the theme.
+Note that `flat` and `flatSpacedCaps` take a `DocumentColor`
+argument — the section title colour is the preset's signature
+accent, and the widget deliberately surfaces it as a parameter
+rather than burying it in the theme.
### Composing a preset from widgets
diff --git a/src/main/java/com/demcha/compose/document/templates/cv/v2/package-info.java b/src/main/java/com/demcha/compose/document/templates/cv/v2/package-info.java
index 0fa4f9e3..45d7007b 100644
--- a/src/main/java/com/demcha/compose/document/templates/cv/v2/package-info.java
+++ b/src/main/java/com/demcha/compose/document/templates/cv/v2/package-info.java
@@ -20,15 +20,19 @@
* │ presets/ │
* │ BoxedSections ← composition: data + theme + render │
* │ MinimalUnderlined ← another composition, same pieces │
- * │ ModernProfessional ← third preset, partial widget use │
+ * │ ModernProfessional ← corporate composition variant │
+ * │ CenteredHeadline ← classic centred headline variant │
* └─────────────────────────────────────────────────────────────┘
* │ compose from
* ▼
* ┌─────────────────────────────────────────────────────────────┐
* │ widgets/ ← named visual building blocks (LEGO bricks) │
* │ Headline .spacedCentered | .rightAligned │
- * │ ContactLine .centered | .rightAligned │
+ * │ Subheadline .centeredSpacedCaps │
+ * │ ContactLine .centered | .rightAligned │
+ * │ .twoRowRightAligned │
* │ SectionHeader .banner | .underlined | .flat │
+ * │ .flatSpacedCaps │
* └─────────────────────────────────────────────────────────────┘
* │ delegate to │ read tokens from
* ▼ ▼
@@ -70,8 +74,9 @@
* {@code widgets/}
* The LEGO bricks. "Which visual building block do I
* want here — a banner, an underlined title, a right-aligned
- * headline?" Each widget has 2-3 named variants and a
- * lower-level entry for ad-hoc parameter combinations. This
+ * headline?" Each widget has a small set of named
+ * variants and, where useful, a lower-level entry for ad-hoc
+ * parameter combinations. This
* is where most preset code lives — picking widgets and
* composing them.
*
diff --git a/src/main/java/com/demcha/compose/document/templates/cv/v2/presets/CenteredHeadline.java b/src/main/java/com/demcha/compose/document/templates/cv/v2/presets/CenteredHeadline.java
new file mode 100644
index 00000000..0c2d4589
--- /dev/null
+++ b/src/main/java/com/demcha/compose/document/templates/cv/v2/presets/CenteredHeadline.java
@@ -0,0 +1,234 @@
+package com.demcha.compose.document.templates.cv.v2.presets;
+
+import com.demcha.compose.document.api.DocumentSession;
+import com.demcha.compose.document.dsl.PageFlowBuilder;
+import com.demcha.compose.document.dsl.SectionBuilder;
+import com.demcha.compose.document.node.TextAlign;
+import com.demcha.compose.document.style.DocumentInsets;
+import com.demcha.compose.document.style.DocumentTextDecoration;
+import com.demcha.compose.document.style.DocumentTextStyle;
+import com.demcha.compose.document.templates.api.DocumentTemplate;
+import com.demcha.compose.document.templates.cv.v2.components.MarkdownInline;
+import com.demcha.compose.document.templates.cv.v2.components.SectionDispatcher;
+import com.demcha.compose.document.templates.cv.v2.data.CvDocument;
+import com.demcha.compose.document.templates.cv.v2.data.CvRow;
+import com.demcha.compose.document.templates.cv.v2.data.CvSection;
+import com.demcha.compose.document.templates.cv.v2.data.RowsSection;
+import com.demcha.compose.document.templates.cv.v2.data.RowStyle;
+import com.demcha.compose.document.templates.cv.v2.data.Slot;
+import com.demcha.compose.document.templates.cv.v2.theme.CvTheme;
+import com.demcha.compose.document.templates.cv.v2.widgets.ContactLine;
+import com.demcha.compose.document.templates.cv.v2.widgets.Headline;
+import com.demcha.compose.document.templates.cv.v2.widgets.SectionHeader;
+import com.demcha.compose.document.templates.cv.v2.widgets.Subheadline;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * v2 port of the classic "Centered Headline" CV preset.
+ *
+ * Visual signature ported from the legacy v1 preset:
+ *
+ * - Centred letter-spaced uppercase name in Poppins 24pt as the
+ * page's loudest element.
+ * - A small centred {@code P R O F E S S I O N A L T I T L E}
+ * subheadline beneath the name.
+ * - Thin full-width rules above and below the centred contact
+ * line.
+ * - Section titles rendered as quiet small
+ * spaced-caps Lato bold in a soft grey — left-aligned, no
+ * banner panel.
+ * - Body content in Lato 8.7pt with markdown inline emphasis
+ * (the canonical {@link SectionDispatcher} body pipeline does
+ * the heavy lifting; project-style stacked rows are rendered
+ * without bullets for this classic preset).
+ * - Thin inter-module rules separating each section block.
+ *
+ *
+ * The preset reuses three existing widgets ({@link Headline},
+ * {@link ContactLine}, {@link SectionHeader#flatSpacedCaps}) and one
+ * newly introduced widget ({@link Subheadline}). The thin rules
+ * between sections are emitted as inline {@code pageFlow.addLine(...)}
+ * calls because they are part of the page-flow composition, not a
+ * section-scoped widget — turning them into a widget would force every
+ * caller to round-trip through a {@link com.demcha.compose.document.dsl.SectionBuilder},
+ * which is the wrong granularity for a single-line ornament.
+ *
+ * Why the subline says "PROFESSIONAL TITLE" verbatim:
+ * the v2 {@link com.demcha.compose.document.templates.cv.v2.data.CvIdentity}
+ * model does not carry a {@code jobTitle} field today — the v1 preset
+ * hard-coded the same string for visual signature purposes and we
+ * preserve that exactly to keep the baseline matching. When a
+ * {@code jobTitle} field is introduced, swap the hard-coded string
+ * for {@code doc.identity().jobTitle()} (with a sensible fallback).
+ */
+public final class CenteredHeadline {
+
+ /** Stable template identifier. */
+ public static final String ID = "centered-headline";
+
+ /** Human-readable display name. */
+ public static final String DISPLAY_NAME = "Centered Headline";
+
+ /** Recommended page margin (in points) — matches the legacy v1 preset. */
+ public static final double RECOMMENDED_MARGIN = 28.0;
+
+ /**
+ * Fixed subheadline text. Matches the v1 preset's hard-coded
+ * caption verbatim — see class Javadoc for rationale.
+ */
+ private static final String SUBHEADLINE_CAPTION = "Professional Title";
+
+ private CenteredHeadline() {
+ // utility class — not instantiable
+ }
+
+ /**
+ * Builds the preset with the classic Centered Headline theme
+ * ({@link CvTheme#centeredHeadline()}).
+ */
+ public static DocumentTemplate create() {
+ return create(CvTheme.centeredHeadline());
+ }
+
+ /**
+ * Builds the preset with a caller-supplied theme. Allows
+ * variations on the Centered Headline theme (alternate
+ * typography, slightly different palette) without forking this
+ * class.
+ */
+ public static DocumentTemplate create(CvTheme theme) {
+ Objects.requireNonNull(theme, "theme");
+ return new Template(theme);
+ }
+
+ private static final class Template implements DocumentTemplate {
+
+ private final CvTheme theme;
+
+ Template(CvTheme theme) {
+ this.theme = theme;
+ }
+
+ @Override
+ public String id() {
+ return ID;
+ }
+
+ @Override
+ public String displayName() {
+ return DISPLAY_NAME;
+ }
+
+ @Override
+ public void compose(DocumentSession document, CvDocument doc) {
+ Objects.requireNonNull(document, "document");
+ Objects.requireNonNull(doc, "doc");
+
+ double ruleWidth = document.canvas().innerWidth();
+
+ // -- name + subheadline + contact, framed by thin rules ----
+ PageFlowBuilder pageFlow = document.dsl()
+ .pageFlow()
+ .name("CenteredHeadlineRoot")
+ .spacing(theme.spacing().pageFlowSpacing())
+ .addSection("Headline", section -> {
+ Headline.spacedCentered(section, doc.identity().name(), theme);
+ Subheadline.centeredSpacedCaps(section, SUBHEADLINE_CAPTION,
+ subheadlineStyle());
+ })
+ .addLine(line -> rule(line, "HeadlineRule", ruleWidth, 7, 0))
+ .addSection("Contact",
+ section -> ContactLine.centered(section, doc.identity(), theme))
+ .addLine(line -> rule(line, "ContactRule", ruleWidth, 0, 8));
+
+ // -- sections — flatSpacedCaps title, dispatched body,
+ // thin inter-module rule between consecutive sections.
+ List sections = doc.sectionsIn(Slot.MAIN);
+ for (int i = 0; i < sections.size(); i++) {
+ final CvSection sec = sections.get(i);
+ final int idx = i;
+ pageFlow.addSection("Title_" + idx, host ->
+ SectionHeader.flatSpacedCaps(host, sec.title(),
+ theme.palette().muted(), theme, null));
+ pageFlow.addLine(line ->
+ rule(line, "TitleBottomRule_" + idx, ruleWidth, 8, 8));
+ pageFlow.addSection("Body_" + idx, host ->
+ renderBody(host, sec));
+ if (idx < sections.size() - 1) {
+ pageFlow.addLine(line ->
+ rule(line, "InterModuleRule_" + idx, ruleWidth, 5, 8));
+ }
+ }
+
+ pageFlow.build();
+ }
+
+ private void renderBody(SectionBuilder host, CvSection sec) {
+ if (sec instanceof RowsSection rows
+ && rows.style() == RowStyle.BULLETED_STACKED) {
+ host.spacing(theme.spacing().sectionBodySpacing())
+ .padding(theme.spacing().sectionBodyPadding());
+ for (int i = 0; i < rows.rows().size(); i++) {
+ if (i > 0) {
+ host.spacer(0, theme.spacing().entrySeparation());
+ }
+ renderStackedProject(host, rows.rows().get(i));
+ }
+ return;
+ }
+ SectionDispatcher.renderBody(host, sec, theme);
+ }
+
+ private void renderStackedProject(SectionBuilder host, CvRow row) {
+ DocumentTextStyle titleStyle = theme.bodyBoldStyle();
+ DocumentTextStyle bodyStyle = theme.bodyStyle();
+ host.addParagraph(p -> p
+ .text(row.label())
+ .textStyle(titleStyle)
+ .align(TextAlign.LEFT)
+ .lineSpacing(theme.typography().bodyLineSpacing())
+ .margin(DocumentInsets.top((float) theme.spacing().paragraphMarginTop())));
+ if (!row.body().isBlank()) {
+ host.addParagraph(p -> p
+ .textStyle(bodyStyle)
+ .align(TextAlign.LEFT)
+ .lineSpacing(theme.typography().bodyLineSpacing())
+ .margin(DocumentInsets.zero())
+ .rich(rich -> MarkdownInline.append(rich, row.body(), bodyStyle)));
+ }
+ }
+
+ /**
+ * Builds the subheadline text style. Inlined here (not a theme
+ * slot) because no other widget reaches for this exact
+ * combination — Subheadline takes the style as a parameter so
+ * presets stay in control of the visual weight of their
+ * captions.
+ */
+ private DocumentTextStyle subheadlineStyle() {
+ return DocumentTextStyle.builder()
+ .fontName(theme.typography().headlineFont())
+ .size(8.6)
+ .decoration(DocumentTextDecoration.DEFAULT)
+ .color(theme.palette().muted())
+ .build();
+ }
+
+ /**
+ * Configure a thin full-width horizontal rule. The widths and
+ * margin pattern match the v1 preset exactly: top inset
+ * creates breathing room above the rule, bottom inset before
+ * the next paragraph.
+ */
+ private void rule(com.demcha.compose.document.dsl.LineBuilder line,
+ String name, double width, double top, double bottom) {
+ line.name(name)
+ .horizontal(width)
+ .color(theme.palette().rule())
+ .thickness(theme.spacing().accentRuleWidth())
+ .margin(new DocumentInsets(top, 0, bottom, 0));
+ }
+ }
+}
diff --git a/src/main/java/com/demcha/compose/document/templates/cv/v2/theme/CvPalette.java b/src/main/java/com/demcha/compose/document/templates/cv/v2/theme/CvPalette.java
index 7f56ee3c..ca8103af 100644
--- a/src/main/java/com/demcha/compose/document/templates/cv/v2/theme/CvPalette.java
+++ b/src/main/java/com/demcha/compose/document/templates/cv/v2/theme/CvPalette.java
@@ -36,4 +36,24 @@ public static CvPalette classic() {
DocumentColor.rgb(170, 170, 170),
DocumentColor.rgb(220, 226, 230));
}
+
+ /**
+ * Soft greyscale palette ported from the original
+ * {@code CenteredHeadline} v1 preset — slightly warmer than
+ * {@link #classic()} with a higher-contrast headline tone and a
+ * paler rule colour suited to thin full-width separators.
+ *
+ * The {@code banner} slot is required by the record but unused
+ * by the Centered Headline visual signature (no banner panels);
+ * we reuse the classic banner colour so themes can swap the body
+ * style without leaving an obvious gap if a future preset reuses
+ * this palette with a banner-style section header.
+ */
+ public static CvPalette centeredHeadline() {
+ return new CvPalette(
+ DocumentColor.rgb(54, 54, 54), // ink (#363636)
+ DocumentColor.rgb(105, 105, 105), // muted / soft (#696969)
+ DocumentColor.rgb(188, 188, 188), // rule (#BCBCBC)
+ DocumentColor.rgb(220, 226, 230)); // banner (inherits classic)
+ }
}
diff --git a/src/main/java/com/demcha/compose/document/templates/cv/v2/theme/CvSpacing.java b/src/main/java/com/demcha/compose/document/templates/cv/v2/theme/CvSpacing.java
index 97288def..c900d5bb 100644
--- a/src/main/java/com/demcha/compose/document/templates/cv/v2/theme/CvSpacing.java
+++ b/src/main/java/com/demcha/compose/document/templates/cv/v2/theme/CvSpacing.java
@@ -117,6 +117,32 @@ public static CvSpacing classic() {
3.0); // entrySeparation
}
+ /**
+ * Spacing for the {@code CenteredHeadline} preset — designed for a
+ * classic single-column resume with thin full-width rules above
+ * and below the contact line, and small inter-module rules between
+ * sections. Banner-panel fields are left at sensible defaults but
+ * unused (the preset uses {@code flatSpacedCaps} section headers,
+ * not banners).
+ */
+ public static CvSpacing centeredHeadline() {
+ return new CvSpacing(
+ 0, // pageFlowSpacing (zero — rules supply visual gaps)
+ 1.5, // sectionBodySpacing
+ DocumentInsets.zero(), // sectionBodyPadding
+ new DocumentInsets(8, 0, 0, 0), // headlinePadding (small top breathing room)
+ new DocumentInsets(7, 0, 7, 0), // contactPadding
+ 0.0, // bannerCornerRadius (unused)
+ 5.0, // bannerInnerPadding (unused)
+ DocumentInsets.top(0), // bannerMargin (unused)
+ 0.55, // accentRuleWidth (thin v1 rule)
+ 1.2, // paragraphMarginTop
+ 8.0, // entryHeaderRowSpacing
+ 1.0, // entryTitleWeight
+ 0.45, // entryDateWeight
+ 3.0); // entrySeparation
+ }
+
/**
* Tighter spacing for the Modern Professional preset — no banner
* panels, denser body, single-page-friendly proportions.
diff --git a/src/main/java/com/demcha/compose/document/templates/cv/v2/theme/CvTheme.java b/src/main/java/com/demcha/compose/document/templates/cv/v2/theme/CvTheme.java
index a7cb88e3..4652c83e 100644
--- a/src/main/java/com/demcha/compose/document/templates/cv/v2/theme/CvTheme.java
+++ b/src/main/java/com/demcha/compose/document/templates/cv/v2/theme/CvTheme.java
@@ -87,6 +87,21 @@ public static CvTheme modernProfessional() {
CvDecoration.classic());
}
+ /**
+ * The "Centered Headline" classic look ported from the v1 preset
+ * of the same name — Poppins headline, Lato body, soft greyscale
+ * palette, thin full-width rules separating headline / contact /
+ * each module. Pipe contact separator matches the classic
+ * decoration.
+ */
+ public static CvTheme centeredHeadline() {
+ return new CvTheme(
+ CvPalette.centeredHeadline(),
+ CvTypography.centeredHeadline(),
+ CvSpacing.centeredHeadline(),
+ CvDecoration.classic());
+ }
+
// -- pre-built text-style helpers ------------------------------------
// Renderers ask the theme for an already-composed DocumentTextStyle
// instead of re-assembling font + size + decoration + colour every
diff --git a/src/main/java/com/demcha/compose/document/templates/cv/v2/theme/CvTypography.java b/src/main/java/com/demcha/compose/document/templates/cv/v2/theme/CvTypography.java
index 1e4dea62..7de1427b 100644
--- a/src/main/java/com/demcha/compose/document/templates/cv/v2/theme/CvTypography.java
+++ b/src/main/java/com/demcha/compose/document/templates/cv/v2/theme/CvTypography.java
@@ -78,4 +78,28 @@ public static CvTypography modernProfessional() {
10.0, // body
1.35); // line spacing
}
+
+ /**
+ * Poppins headline + Lato body scale ported from the original
+ * {@code CenteredHeadline} v1 preset. The headline is the page's
+ * loudest element (24pt spaced caps); everything else is at
+ * 8-9pt for a classic-resume density.
+ *
+ * {@code sizeBanner} feeds the
+ * {@link com.demcha.compose.document.templates.cv.v2.widgets.SectionHeader#flatSpacedCaps}
+ * variant — small bold spaced-caps title in the soft palette
+ * tone.
+ */
+ public static CvTypography centeredHeadline() {
+ return new CvTypography(
+ FontName.POPPINS, FontName.LATO,
+ 24.0, // headline (spaced-caps name)
+ 8.3, // contact
+ 9.5, // banner (used as small spaced-caps section title)
+ 8.8, // entry title
+ 8.6, // entry date
+ 8.4, // entry subtitle
+ 8.7, // body
+ 1.45); // line spacing
+ }
}
diff --git a/src/main/java/com/demcha/compose/document/templates/cv/v2/widgets/SectionHeader.java b/src/main/java/com/demcha/compose/document/templates/cv/v2/widgets/SectionHeader.java
index 260ce84f..f5239886 100644
--- a/src/main/java/com/demcha/compose/document/templates/cv/v2/widgets/SectionHeader.java
+++ b/src/main/java/com/demcha/compose/document/templates/cv/v2/widgets/SectionHeader.java
@@ -23,6 +23,11 @@
* {@link #flat} — large left-aligned bold title in a given
* colour, no panel, no rule. Visual signature of
* {@code ModernProfessional}.
+ * {@link #flatSpacedCaps} — small left-aligned spaced-caps bold
+ * title in a given colour, no panel, no rule. Quieter than
+ * {@link #flat}; visual signature of {@code CenteredHeadline}
+ * (and likely {@code NordicClean} / {@code ClassicSerif} when
+ * ported).
*
*
* Unlike {@link Headline} (one rendering shape, two text
@@ -96,4 +101,45 @@ public static void flat(SectionBuilder host, String title,
.align(TextAlign.LEFT)
.margin(DocumentInsets.zero()));
}
+
+ /**
+ * Small left-aligned spaced-caps bold title in a given colour. No
+ * panel, no rule — flat like {@link #flat} but typographically
+ * quieter: body font, body-sized, transformed to letter-spaced
+ * uppercase via {@link TextOrnaments#spacedUpper(String)}. Visual
+ * signature of {@code CenteredHeadline}.
+ *
+ *
If the {@code titleStyle} parameter is {@code null} the widget
+ * derives a style from the theme's body typography
+ * ({@code bodyFont} at {@code sizeBanner} weight, bold, the given
+ * colour). Pass an explicit style when the preset needs a specific
+ * font / size combination that doesn't map to a theme slot.
+ *
+ * @param host host section
+ * @param title verbatim title text (transformed to spaced caps
+ * by the widget)
+ * @param color title colour — typically a soft / muted accent
+ * @param theme active theme (used as the style default and
+ * for the padding token)
+ * @param titleStyle explicit style override; pass {@code null} to
+ * fall back to the theme-derived default
+ */
+ public static void flatSpacedCaps(SectionBuilder host, String title,
+ DocumentColor color, CvTheme theme,
+ DocumentTextStyle titleStyle) {
+ DocumentTextStyle resolved = titleStyle != null
+ ? titleStyle
+ : DocumentTextStyle.builder()
+ .fontName(theme.typography().bodyFont())
+ .size(theme.typography().sizeBanner())
+ .decoration(DocumentTextDecoration.BOLD)
+ .color(color)
+ .build();
+ host.padding(new DocumentInsets(0, 0, 0, 0))
+ .addParagraph(p -> p
+ .text(TextOrnaments.spacedUpper(title))
+ .textStyle(resolved)
+ .align(TextAlign.LEFT)
+ .margin(DocumentInsets.zero()));
+ }
}
diff --git a/src/main/java/com/demcha/compose/document/templates/cv/v2/widgets/Subheadline.java b/src/main/java/com/demcha/compose/document/templates/cv/v2/widgets/Subheadline.java
new file mode 100644
index 00000000..60b6e59d
--- /dev/null
+++ b/src/main/java/com/demcha/compose/document/templates/cv/v2/widgets/Subheadline.java
@@ -0,0 +1,66 @@
+package com.demcha.compose.document.templates.cv.v2.widgets;
+
+import com.demcha.compose.document.dsl.SectionBuilder;
+import com.demcha.compose.document.node.TextAlign;
+import com.demcha.compose.document.style.DocumentInsets;
+import com.demcha.compose.document.style.DocumentTextStyle;
+import com.demcha.compose.document.templates.cv.v2.components.TextOrnaments;
+
+/**
+ * Secondary headline widget — the small spaced-caps tagline rendered
+ * directly beneath the main {@link Headline} (e.g.
+ * {@code P R O F E S S I O N A L T I T L E}).
+ *
+ * When to use
+ *
+ * Reach for {@code Subheadline} when a preset stacks a quieter
+ * descriptor under the subject's name — typical of classic /
+ * editorial layouts where the headline block reads as two centred
+ * lines: a loud name and a soft caption. Visual signature of
+ * {@code CenteredHeadline}; will likely fit
+ * {@code EditorialBlue} when ported.
+ *
+ * Variants
+ *
+ *
+ * - {@link #centeredSpacedCaps} — centred, transformed to
+ * letter-spaced uppercase. The canonical use today.
+ *
+ *
+ * If a future preset wants a right-aligned or verbatim (non-spaced)
+ * subheadline, add a sibling factory here — keep the
+ * {@code (host, text, style)} signature shape so call sites stay
+ * uniform. Padding is supplied by the caller via {@link SectionBuilder}
+ * because subheadline insets are part of the headline block's vertical
+ * rhythm, not a per-widget concern.
+ */
+public final class Subheadline {
+
+ private Subheadline() {
+ }
+
+ /**
+ * Centred letter-spaced uppercase subheadline. Text is transformed
+ * through {@link TextOrnaments#spacedUpper(String)} — pass the raw
+ * caption ({@code "Professional Title"}) and the widget handles the
+ * spacing.
+ *
+ * @param host host section (typically the same section that
+ * hosts the main {@link Headline})
+ * @param text caption text to render, before the spaced-caps
+ * transform
+ * @param style explicit text style — the subheadline has no
+ * dedicated theme slot, so the caller composes
+ * {@code font + size + decoration + colour} and hands
+ * it in. Centralise the style in the preset, not at
+ * each call site.
+ */
+ public static void centeredSpacedCaps(SectionBuilder host, String text,
+ DocumentTextStyle style) {
+ host.addParagraph(p -> p
+ .text(TextOrnaments.spacedUpper(text))
+ .textStyle(style)
+ .align(TextAlign.CENTER)
+ .margin(DocumentInsets.top(1)));
+ }
+}
diff --git a/src/main/java/com/demcha/compose/document/templates/cv/v2/widgets/package-info.java b/src/main/java/com/demcha/compose/document/templates/cv/v2/widgets/package-info.java
index 90ce6ac5..308012f6 100644
--- a/src/main/java/com/demcha/compose/document/templates/cv/v2/widgets/package-info.java
+++ b/src/main/java/com/demcha/compose/document/templates/cv/v2/widgets/package-info.java
@@ -5,7 +5,7 @@
* (addParagraph / softPanel / accentBottom) and CV-specific
* renderers in {@code components/}. Each widget captures one
* visual idea (a headline, a contact line, a section
- * header) with two or three named variants, so a preset author
+ * header) with a small set of named variants, so a preset author
* composes a page by picking widgets instead of
* writing rendering DSL by hand.
*
@@ -44,12 +44,16 @@
* {@link com.demcha.compose.document.templates.cv.v2.widgets.Headline}
* — top-of-document name in 2 variants
* ({@code spacedCentered}, {@code rightAligned}).
+ * {@link com.demcha.compose.document.templates.cv.v2.widgets.Subheadline}
+ * — secondary tagline under the name in 1 variant
+ * ({@code centeredSpacedCaps}).
* {@link com.demcha.compose.document.templates.cv.v2.widgets.ContactLine}
- * — pipe-separated contact + links row in 2 variants
- * ({@code centered}, {@code rightAligned}).
+ * — pipe-separated contact + links row in 3 variants
+ * ({@code centered}, {@code rightAligned},
+ * {@code twoRowRightAligned}).
* {@link com.demcha.compose.document.templates.cv.v2.widgets.SectionHeader}
- * — section title in 3 variants ({@code banner},
- * {@code underlined}, {@code flat}).
+ * — section title in 4 variants ({@code banner},
+ * {@code underlined}, {@code flat}, {@code flatSpacedCaps}).
*
*
* Each widget delegates internally to the lower-level renderers
diff --git a/src/test/java/com/demcha/compose/document/templates/cv/v2/presets/CvV2VisualParityTest.java b/src/test/java/com/demcha/compose/document/templates/cv/v2/presets/CvV2VisualParityTest.java
index c2c469d8..b4fe7583 100644
--- a/src/test/java/com/demcha/compose/document/templates/cv/v2/presets/CvV2VisualParityTest.java
+++ b/src/test/java/com/demcha/compose/document/templates/cv/v2/presets/CvV2VisualParityTest.java
@@ -92,7 +92,10 @@ private static Stream presets() {
(Supplier>) MinimalUnderlined::create),
Arguments.of("modern_professional",
ModernProfessional.RECOMMENDED_MARGIN,
- (Supplier>) ModernProfessional::create));
+ (Supplier>) ModernProfessional::create),
+ Arguments.of("centered_headline",
+ CenteredHeadline.RECOMMENDED_MARGIN,
+ (Supplier>) CenteredHeadline::create));
}
/**
@@ -136,17 +139,17 @@ private static CvDocument canonicalDocument() {
.section(EntriesSection.builder("Education & Certifications")
.entry("MSc Computer Science",
"University of Manchester",
- "2021",
+ "2020-2021",
"Distinction. Thesis: *Composable layout primitives "
+ "for deterministic document rendering*.")
.entry("BSc Software Engineering",
"Imperial College London",
- "2019",
+ "2016-2019",
"First-class honours. Specialisation in compilers and "
+ "static analysis.")
.entry("Oracle Java Certification",
"Professional track",
- "2023",
+ "2023-2024",
"Java 17 platform deep-dive: records, sealed types, "
+ "pattern matching, virtual threads.")
.build())
diff --git a/src/test/java/com/demcha/compose/document/templates/cv/v2/widgets/WidgetSmokeTest.java b/src/test/java/com/demcha/compose/document/templates/cv/v2/widgets/WidgetSmokeTest.java
index 287e639b..b1e1f9f3 100644
--- a/src/test/java/com/demcha/compose/document/templates/cv/v2/widgets/WidgetSmokeTest.java
+++ b/src/test/java/com/demcha/compose/document/templates/cv/v2/widgets/WidgetSmokeTest.java
@@ -48,6 +48,14 @@ void contactLine_variants_render_without_throwing() throws Exception {
});
}
+ @Test
+ void subheadline_variants_render_without_throwing() throws Exception {
+ CvTheme theme = CvTheme.centeredHeadline();
+ renderWithSection(section ->
+ Subheadline.centeredSpacedCaps(section, "Professional Title",
+ theme.bodyStyle()));
+ }
+
@Test
void sectionHeader_variants_render_without_throwing() throws Exception {
CvTheme theme = CvTheme.boxedClassic();
@@ -58,6 +66,9 @@ void sectionHeader_variants_render_without_throwing() throws Exception {
renderWithSection(section ->
SectionHeader.flat(section, "Experience",
DocumentColor.rgb(41, 128, 185), theme));
+ renderWithSection(section ->
+ SectionHeader.flatSpacedCaps(section, "Projects",
+ theme.palette().muted(), theme, null));
}
@Test
diff --git a/src/test/resources/visual-baselines/cv-v2-layered/boxed_sections-page-0.png b/src/test/resources/visual-baselines/cv-v2-layered/boxed_sections-page-0.png
index 5b7f57fda527501208d456f1eb3703489e9598a2..9ce90da029250185340843341afbc463bb476a88 100644
GIT binary patch
delta 71943
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