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
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@
All notable changes to GraphCompose are documented here. Versions
follow semantic versioning; release dates are ISO 8601.

## v1.6.5 — Planned

### Templates v2

- Added the `CenteredHeadline` CV preset to the `cv/v2` layered
template surface, including its isolated theme tokens, visual
regression baselines, and reusable `Subheadline` /
`SectionHeader.flatSpacedCaps` widget support.

## v1.6.4 — 2026-05-22

Bug fix + structured-block patch. Adds two new public Block types —
Expand Down
2 changes: 1 addition & 1 deletion docs/templates/v2-layered/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
29 changes: 19 additions & 10 deletions docs/templates/v2-layered/authoring-presets.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -59,9 +59,9 @@ visual decision you can read like a recipe.
<a id="the-widget-catalog"></a>
## 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

Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
16 changes: 9 additions & 7 deletions docs/templates/v2-layered/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 ↓
Expand Down
8 changes: 5 additions & 3 deletions docs/templates/v2-layered/using-templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -170,13 +171,14 @@ preset renders them. The slot model is opt-in.
<a id="picking-a-preset"></a>
## 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).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
* ▼ ▼
Expand Down Expand Up @@ -70,8 +74,9 @@
* <dt><b>{@code widgets/}</b></dt>
* <dd>The LEGO bricks. <em>"Which visual building block do I
* want here — a banner, an underlined title, a right-aligned
* headline?"</em> Each widget has 2-3 named variants and a
* lower-level entry for ad-hoc parameter combinations. This
* headline?"</em> 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.</dd>
*
Expand Down
Loading