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
96 changes: 74 additions & 22 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -921,6 +921,44 @@ follow semantic versioning; release dates are ISO 8601.

### Templates

- **Five CV presets name their sections with the headings resume parsers look for.** A
resume parser finds a section by matching its heading against a short list of names such
as Experience, Education and Skills, so a heading outside that list can hide a section
that is plainly on the page. These presets print a heading of their own in place of the
document's section title: `ClassicSerif` now prints "Skills" for "Core Skills";
`EditorialBlue` "EXPERIENCE" and "SKILLS" for "EMPLOYMENT HISTORY" and "KEY SKILLS";
`SidebarPortrait` "Skills" for "Key Skills"; `MonogramSidebar` "SKILLS" for "EXPERTISE";
and `EngineeringResume` "Skills", "Education" and "Experience" for "Core Stack",
"Learning" and "Leadership Experience". Only the words change: every heading keeps its
font, colour, tracking and rules. The showcase samples follow suit — the sample CV titles
its education section "Education" rather than "Education & Certifications", and the
Orange Ops, Teal Pulse, Slate Orange, Violet Grid and Terracotta Rail samples title their
skills "SKILLS".

- **Four sidebar CVs draw the name first, and the page looks the same.** A parser that reads
the content stream rather than the page takes the name, the email and the phone from the
lines above the first section heading. `NavySidebar`, `CharcoalGold` and `SidebarPortrait`
drew their sidebar before the main column, and `SlateOrange` its monogram tile before the
identity strip, so OpenResume and resume-parser-ats met "CONTACT", "DH" or a contact line
first and took it for the name. The three sidebar presets' page grid is now a layer stack,
atomic like the row it replaces, that draws the name, then the sidebar, then the rest of
the main column under a stand-in as tall as the name. Drawing the whole main column first
would trade the email for the name, because its first heading would then come before the
sidebar's contact lines. `SlateOrange`'s masthead cells become layers, identity strip
first. At 100 dpi no pixel changes; Sidebar Portrait's main column sits 0.0005pt lower,
because the stand-in's height comes from a layout snapshot rounded to a thousandth of a
point. `CvPresetTextLayerTest` holds the four to drawing the name first.

- **Professional Sidebar and Terracotta Rail carry a longer CV onto more pages.** Both
composed their two columns as one row, and a row is atomic, so a CV longer than one sheet
raised `AtomicNodeTooLargeException`. A CV that fits keeps the single row it always had,
and its output is unchanged. A longer one is packed a whole block at a time — a role, a
project, a sidebar list — onto one row per page, each row after the first behind a page
break. A heading stays with its first entry, a page's roles share one rail in Terracotta
Rail, and a block taller than a page is refused with `AtomicNodeTooLargeException` naming
the block. `ColumnPagesTest` covers the packing, and each preset gains a two-page layout
snapshot.

- **A stacked row's body now hangs under its name, not a couple of points to the left of
it.** The shared bullet path every themed CV preset draws its rows through —
`RowRenderer`, `SkillsRenderer` — put the glyph inside the paragraph's text as a
Expand Down Expand Up @@ -1461,7 +1499,7 @@ follow semantic versioning; release dates are ISO 8601.
refusal of a body taller than one), an exact layout snapshot and a pixel-parity gate;
the examples showcase gains `cv-teal-pulse-v2`.

- **An architect's two-column CV preset: `TerracottaRail`.** A one-page A4 sheet whose
- **An architect's two-column CV preset: `TerracottaRail`.** An A4 sheet whose
narrow column carries a serif monogram over a terracotta rule, the contact channels
behind their marks, two bulleted lists and a block of closing facts, beside a wide
column carrying a letter-spaced masthead, the summary, the roles held on a ringed
Expand All @@ -1485,13 +1523,12 @@ follow semantic versioning; release dates are ISO 8601.
size for every document. The departure is exactly measured — two of 154 nodes narrow,
1 743 of 2 173 720 pixels change, and nothing moves vertically — and both baselines
were recorded with it. A link takes the mark of the network it points at, or a globe.
Like its siblings it holds one page: the body is a single atomic row, so a longer CV raises
`AtomicNodeTooLargeException` rather than flowing or dropping entries. Guarded by a
smoke test (including the unknown-mark data error, an entry with no mark, an identity
with no links, a document with nothing but an identity, the monogram, the link
targets on every kind of title, the four contact rows sharing one axis and the
one-page limit), an exact layout snapshot and a pixel-parity gate;
the examples showcase gains `cv-terracotta-rail-v2`.
A CV longer than the page continues onto more pages. Guarded by a smoke test (including
the unknown-mark data error, an entry with no mark, an identity with no links, a document
with nothing but an identity, the monogram, the link targets on every kind of title, the
four contact rows sharing one axis, the run onto more pages and the refusal of a role
taller than the page), an exact layout snapshot and a pixel-parity gate; the examples
showcase gains `cv-terracotta-rail-v2`.

- **The first invoice preset that paginates what it ports: `LumaStudioInvoice`.** A
studio invoice built around a cream sidebar — the two-line monogram and the wordmark
Expand Down Expand Up @@ -1575,8 +1612,8 @@ follow semantic versioning; release dates are ISO 8601.
shows, subtracting the blank a line box already carries above and below its own type.
Eight berths reach their sections by title, the projects and achievements take the
mark each entry names in `CvEntry.icon()` from this preset's own vocabulary, and the
employer's city and the campus come from `CvEntry.place()`. Like its siblings it owns
its page and holds one: the body is a single atomic row, so a longer CV raises
employer's city and the campus come from `CvEntry.place()`. It owns its page and holds
one: the body is a single atomic row, so a longer CV raises
`AtomicNodeTooLargeException` rather than flowing or dropping entries. Guarded by a
smoke test (including the unknown-mark data error, an entry with no mark, an employer
with no place, the overlapping soft-skills berth, the link targets and the one-page
Expand All @@ -1589,8 +1626,8 @@ follow semantic versioning; release dates are ISO 8601.
the roles held on a timeline rail with a filled marker at each one, and the
achievements and certifications behind badged headings. Ships as
`cv.presets.NavySidebar` on the existing `CvDocument` model, porting the rendered
layout of the published standalone `navy-sidebar-cv` template. Like its sibling it
owns its page and holds one: the two columns are a single atomic row, so a CV longer
layout of the published standalone `navy-sidebar-cv` template. It owns its page and
holds one: the two columns are a single layer stack, which is atomic, so a CV longer
than the sheet raises `AtomicNodeTooLargeException` rather than flowing or dropping
entries. Sections reach their berth by title rather than by `Slot`; languages are a
`RowsSection` because this design writes the proficiency out — "Native", "Advanced" —
Expand All @@ -1606,28 +1643,27 @@ follow semantic versioning; release dates are ISO 8601.
and the fields it has no place for), an exact layout snapshot and a pixel-parity
gate; the examples showcase gains `cv-navy-sidebar-v2`.

- **The first CV preset that owns its page: `ProfessionalSidebar`.** A one-page CV
in two columns on the Barlow Condensed / Lato pair — a navy monogram plate over a
- **The first CV preset that owns its page: `ProfessionalSidebar`.** A CV in two columns
on the Barlow Condensed / Lato pair — a navy monogram plate over a
pale sidebar carrying the contact channels, meter-bar skills, an education rail with
dot markers and five-dot language ratings, beside a white column of the tracked name,
the profile, the roles held with bulleted highlights, the projects and the references
note. Ships as `cv.presets.ProfessionalSidebar` on the existing `CvDocument` model —
no model change was needed — porting the rendered layout of the published standalone
`professional-sidebar-cv` template. The preset owns its page: a 491.6 x 737.28pt
sheet with no margin, the page fill and the pale sidebar painted as page backgrounds. The
sheet holds one page — its two columns are a single atomic row, so a CV longer than the
sheet raises `AtomicNodeTooLargeException` rather than flowing onto a second page or
silently dropping entries the way the capped sidebar presets do. The class documentation
says so, points at `TimelineMinimal` for a preset that splits its own columns, and
sheet with no margin, the page fill and the pale sidebar painted as page backgrounds. A CV
longer than the sheet continues onto more pages rather than silently dropping entries the
way the capped sidebar presets do; the class documentation says so, and
`docs/templates/v2-layered/using-templates.md` carries it beside the capped presets.
Sections reach their berth by title rather than by `Slot`,
because the columns are fixed; skills and languages are both `SkillsSection`s drawn
differently, and a level the document omits draws the name alone. The contact channels
come off `CvIdentity`, with `tel:` and `mailto:` targets built from the values and the
packaged marks chosen per channel. Guarded by a smoke test (including the identity-only
document, the unlevelled skill, the PDF link targets, the one-page limit and the fields
this design has no place for), an exact layout snapshot and a pixel-parity gate; the
examples showcase gains `cv-professional-sidebar-v2`.
document, the unlevelled skill, the PDF link targets, the run onto more pages, the refusal
of a role taller than the sheet and the fields this design has no place for), an exact
layout snapshot and a pixel-parity gate; the examples showcase gains
`cv-professional-sidebar-v2`.

- **A second structured proposal preset: `EditorialProposal`.** The same document the
`NorthlineProposal` preset renders, in a different hand: an orange accent, section
Expand Down Expand Up @@ -2057,6 +2093,22 @@ follow semantic versioning; release dates are ISO 8601.
marked types and members, so a consumer can tell a beta package from a package whose
one admitted type happens to be beta.

- **Every CV preset is either ATS-friendly or design-first, and the showcase says which.**
`docs/templates/v2-layered/using-templates.md` describes the two categories and lists the
eight ATS-friendly presets and the one parser limitation known among them: ATS Reader does
not recognise `ModernProfessional`'s multi-word headings "Professional Experience" and
"Technical Skills". Each preset's showcase sample was checked for text extraction, section
recognition and reading order with the OpenResume parser (pdf.js), ATS Reader (pdfplumber)
and resume-parser-ats (pdf-parse); that is not a claim about every ATS product, and a
document with different content can parse differently. A design-first preset keeps the
layout that is the point of its design — a sidebar, columns, a monogram — at the cost of
at least one of those checks. On the showcase site a CV card carries an "ATS-friendly"
chip, linking to that section, only when its classification earns one.
`ShowcaseAtsEvidenceTest` renders each badged sample deterministically and holds it to the
SHA-256 of the exact PDF its check read, and holds the sample its example publishes to that
render page by page, so the build fails when either changes, until the check is run on the
new render.

## v2.3.0 — 2026-08-31

### Public API
Expand Down
Binary file modified assets/readme/examples/cv-blue-banner-v2.pdf
Binary file not shown.
Binary file modified assets/readme/examples/cv-boxed-sections-v2.pdf
Binary file not shown.
Binary file modified assets/readme/examples/cv-centered-headline-v2.pdf
Binary file not shown.
Binary file modified assets/readme/examples/cv-charcoal-gold-v2.pdf
Binary file not shown.
Binary file modified assets/readme/examples/cv-classic-serif-v2.pdf
Binary file not shown.
Binary file modified assets/readme/examples/cv-editorial-blue-v2.pdf
Binary file not shown.
Binary file modified assets/readme/examples/cv-engineering-resume-v2.pdf
Binary file not shown.
Binary file modified assets/readme/examples/cv-executive-v2.pdf
Binary file not shown.
Binary file modified assets/readme/examples/cv-minimal-underlined-v2.pdf
Binary file not shown.
Binary file modified assets/readme/examples/cv-modern-professional-v2.pdf
Binary file not shown.
Binary file modified assets/readme/examples/cv-monogram-sidebar-v2.pdf
Binary file not shown.
Binary file modified assets/readme/examples/cv-navy-sidebar-v2.pdf
Binary file not shown.
Binary file modified assets/readme/examples/cv-orange-ops-v2.pdf
Binary file not shown.
Binary file modified assets/readme/examples/cv-sidebar-portrait-v2.pdf
Binary file not shown.
Binary file modified assets/readme/examples/cv-slate-orange-v2.pdf
Binary file not shown.
Binary file modified assets/readme/examples/cv-teal-pulse-v2.pdf
Binary file not shown.
Binary file modified assets/readme/examples/cv-terracotta-rail-v2.pdf
Binary file not shown.
Binary file modified assets/readme/examples/cv-timeline-minimal-v2.pdf
Binary file not shown.
Binary file modified assets/readme/examples/cv-violet-grid-v2.pdf
Binary file not shown.
54 changes: 44 additions & 10 deletions docs/templates/v2-layered/using-templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,38 @@ BoxedSections.create(BrandTheme.boxedClassic()) // explicit
BoxedSections.create(myCustomTheme) // your own
```

<a id="ats-friendly-presets"></a>
### ATS-friendly and design-first presets

Many applicant tracking systems read a CV with a PDF parser before a person
sees it, so every CV preset falls into one of two categories:

- **ATS-friendly** — validated for text extraction, section recognition and
reading order using multiple independent PDF resume parsers. The showcase
marks these with an "ATS-friendly" badge.
- **Design-first** — the layout is the point of the design: a sidebar,
columns, a monogram. The text still extracts, but the layout costs a parser
at least one of those checks, and the preset keeps its design rather than
trading it away.

The badge is earned, not assumed. Each preset's showcase sample was read with
the OpenResume parser (pdf.js), ATS Reader (pdfplumber) and resume-parser-ats
(pdf-parse), last on 2026-09-14. It is not a claim about every ATS product,
and a document with different content can parse differently.

| ATS-friendly preset | Known parser limitation |
|---|---|
| `BlueBanner.create()` | — |
| `BoxedSections.create()` | — |
| `CenteredHeadline.create()` | — |
| `ClassicSerif.create()` | — |
| `EditorialBlue.create()` | — |
| `Executive.create()` | — |
| `MinimalUnderlined.create()` | — |
| `ModernProfessional.create()` | ATS Reader does not recognise the multi-word headings "Professional Experience" and "Technical Skills"; the other two parsers do |

Every other CV preset is design-first.

### Presets that cap content

Three presets are compositions for a fixed amount of content rather than
Expand All @@ -229,22 +261,24 @@ API or the produced PDF reports that they were dropped.
| `MintEditorial.create()` | 6 expertise labels, 6 skill bars (experience spans both pages in full) |

The caps are load-bearing, not a matter of taste: each of these presets
builds its columns as one `addRow`, and a row is atomic — it fits a page
whole or the paginator raises `AtomicNodeTooLargeException`. Lifting a
builds its columns as one atomic node — a row or a layer stack — that fits
a page whole or the paginator raises `AtomicNodeTooLargeException`. Lifting a
cap without teaching the preset to pick its own page boundaries turns a
CV that silently lost an entry into one that fails to render.

`ProfessionalSidebar.create()`, `NavySidebar.create()`,
`SerifHeadline.create()` and `CharcoalGold.create()` are compositions
for a fixed amount of content that cap nothing: each reproduces a
specific one-page sheet, so a CV longer than that sheet raises
`AtomicNodeTooLargeException` instead of losing an entry to a cap. Size
the document to them — roughly five or six roles with three or four
highlights each, alongside the other blocks.
`NavySidebar.create()`, `SerifHeadline.create()` and
`CharcoalGold.create()` are compositions for a fixed amount of content
that cap nothing: each reproduces a specific one-page sheet, so a CV
longer than that sheet raises `AtomicNodeTooLargeException` instead of
losing an entry to a cap. Size the document to them — roughly five or six
roles with three or four highlights each, alongside the other blocks.

If the document's length is the author's rather than the template's,
pick a preset that paginates — `TimelineMinimal` splits its own columns
pick a preset that paginates. `TimelineMinimal` splits its own columns
and carries every entry it is given onto as many pages as it needs.
`ProfessionalSidebar` and `TerracottaRail` keep the sheet they were drawn
as for a CV that fits it, and carry a longer one onto more pages a whole
role, project or sidebar list at a time.

`NordicClean` also exposes preset-specific options because its
signature has a structural rail and three editable colour surfaces:
Expand Down
7 changes: 7 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,13 @@ catalogue runner generates. The gallery below renders every preset from one entr
instead, and is not part of that run — use it to produce the whole set locally:
[📜 Full source](src/main/java/com/demcha/examples/templates/cv/CvTemplateGalleryFileExample.java)

Eight presets are **ATS-friendly**: their showcase samples are validated for text extraction,
section recognition and reading order using multiple independent PDF resume parsers. They are Blue Banner, Boxed
Sections, Centered Headline, Classic Serif, Editorial Blue, Executive, Minimal Underlined
and Modern Professional, whose multi-word headings ATS Reader misses. The rest are
design-first: their sidebars, columns and monograms stay, at a parser's expense. See
[ATS-friendly and design-first presets](../docs/templates/v2-layered/using-templates.md#ats-friendly-presets).

### Cover letter — template gallery

Generates all paired v2 cover-letter presets in one run — one
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ public static CvDocument sampleCvDocumentV2() {
.build();

EntriesSection education = EntriesSection
.builder("Education & Certifications")
.builder("Education")
.entry("MSc Computer Science",
"University of Manchester",
"2019-2021",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ private static CvIdentity identity() {

/** The skills, as one flat group — the design draws no group names. */
private static SkillsSection skills() {
return new SkillsSection("KEY SKILLS", List.of(SkillGroup.ofNames("KEY SKILLS", List.of(
return new SkillsSection("SKILLS", List.of(SkillGroup.ofNames("SKILLS", List.of(
"Warehouse Operations Management",
"Team Leadership & Development",
"Inventory Control & Accuracy",
Expand Down
Loading
Loading