Skip to content

feat(templates): add the SerifHeadline two-column CV preset - #619

Merged
DemchaAV merged 1 commit into
feature/template-promotionfrom
feat/serif-headline-cv-preset
Aug 31, 2026
Merged

DemchaAV merged 1 commit into
feature/template-promotionfrom
feat/serif-headline-cv-preset

Conversation

@DemchaAV

Copy link
Copy Markdown
Owner

Why

The third CV bundle in the queue is the one that stops being a sidebar. It is a two-column editorial sheet with a serif masthead, a timeline of roles beside grouped skill meters, and two full-width bands of marked cards closing it — 235 laid-out nodes against the 87 and 158 of its predecessors.

It is also the one whose geometry is not a set of numbers somebody typed. The design was drawn on a 1024-pixel grid and every distance in it is that grid put through a scale, with each vertical gap expressed as the ink the drawing shows rather than the margin a line box needs. Porting it meant porting that arithmetic, not its results.

What changed

  • cv/presets/SerifHeadline — a DocumentTemplate<CvDocument> via create(), split for the 500-LOC rule into package-private SerifHeadlineStyles (the grid, the type metrics and the gap arithmetic), SerifHeadlineWidgets (the heading, the plate, the N-column band), SerifHeadlineMasthead, SerifHeadlineMain, SerifHeadlineAside, SerifHeadlineClosing, SerifHeadlineText and SerifHeadlineIcons.
  • The arithmetic came across, not its output. gap(inkPx, blankAbove, blankBelow) states a distance as the white between letters and subtracts what the two line boxes already contribute; heights carry a compression factor on top of the width scale, because the drawing is proportionally taller than A4. Freezing the results as literals would have been unmaintainable the first time a type size moved.
  • The three closing bands are a layer stack, not a row: the marks hang outside their columns and the separators sit exactly on the column edges, both of which a row's slots would clip or push. Every child is placed by computed insets instead.
  • The trailing rule on a full-width heading is authored, so its width comes from an estimate of what the tracked capitals will measure — three advance classes and a 3% margin that errs short. The engine offers no measurement at compose time, and the estimate is documented as one.
  • Eight berths reach their sections by title. Soft skills are matched before skills, because "Soft Skills" names both and the block with a shape of its own should win it — pinned by a test.
  • Projects and achievements take the mark each entry names in CvEntry.icon(); the vocabulary is this preset's own and an unknown token is reported as a data error listing the set. The certification medal is chrome, not data. The employer's city and the campus come from CvEntry.place().
  • The eleven marks ship inside the templates artifact under templates/cv/serif-headline/icons/.

Verification

Full reactor gate (the eight CI modules) → BUILD SUCCESS; the CV preset suites 30/30; examples suite 77/77 including CommittedAssetDriftTest with the new preview; javadoc:javadoc → BUILD SUCCESS with no warning naming a new file.

Port parity, verified against the published template itself: the preset fed the template's own fixture is pixel-identical to the standalone render — 0 / 2,173,720 differing pixels, and the layout snapshot matches node for node — 235 of 235, same names, zero geometry delta. The bundle's geometry is the same on its pinned 2.2.0 and on this branch (235/235 nodes, zero deltas > 0.01pt); the two engines differ only by the released ligature fix (#601), so the current-engine render is the parity target.

Notes

  • The sheet holds one page. The body is a single row, and a row is atomic, so a CV longer than the sheet raises AtomicNodeTooLargeException rather than flowing or dropping entries — the same contract the two sidebar presets landed with, documented on the class, in using-templates.md, and pinned by a test.
  • A certification title wraps inside its narrow column, which is the design rather than a defect: the reference does it too, and the smoke test asserts either side of the break rather than across it.
  • trackedWidth is an estimate, so a heading long enough to make its trailing rule overlap the letters would not go red. The margin errs short and the snapshot freezes the fixture's rules; the method says as much.

Lane: canonical (templates.cv.presets + qa + examples) — no engine changes, no model changes (those landed in #618).

if (hasBody) {
page.addRow("BodyColumns", row -> {
row.spacing(0);
row.gap(0);
static void compose(PageFlowBuilder page, CvIdentity identity, ParagraphSection summary) {
page.addRow("Masthead", row -> {
row.spacing(0);
row.gap(0);
@DemchaAV
DemchaAV merged commit 8979ce0 into feature/template-promotion Aug 31, 2026
12 checks passed
@DemchaAV
DemchaAV deleted the feat/serif-headline-cv-preset branch August 31, 2026 07:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants