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
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,24 @@ follow semantic versioning; release dates are ISO 8601.
colours the packaged marks come in, so a document chooses one deliberately rather
than mixing navy and gold in a row by accident.

- **A photographic CV preset: `CharcoalGold`.** A one-page sheet in two columns: a
charcoal sidebar carrying a ringed photograph, the contact channels behind their
marks, rated skills, languages and degrees, beside a paper column carrying a two-tone
name — the given name in ink, the family name larger and in gold — the summary, the
roles held on a dated rail, a pair of credential columns divided by a hairline, and a
closing strip of tools. Ships as `cv.presets.CharcoalGold` on the existing
`CvDocument` model with **no model change at all**, porting the rendered layout of the
published standalone `charcoal-gold-cv` template. Unlike its ported siblings it leaves
the page size to the caller: the design is drawn on A4 and its geometry follows the
page's own width, so a different page rescales rather than breaks. Seven berths reach
their sections by title; credentials take the mark each entry names in
`CvEntry.icon()` from this preset's own set, every title it draws is a link when the
entry carries one, and a skill the document leaves unlevelled draws no rating rather
than five empty dots. Guarded by a smoke test (including the unknown-mark data error,
the missing portrait, the unlevelled skill, the link targets and the one-page limit),
an exact layout snapshot and a pixel-parity gate; the examples showcase gains
`cv-charcoal-gold-v2`.

- **A two-column editorial CV preset: `SerifHeadline`.** A one-page A4 sheet under a
Volkhov masthead — the name in the display serif over its role and a short gold rule,
the contact channels stacked opposite — then a two-column body: the roles held on a
Expand Down
Binary file added assets/readme/examples/cv-charcoal-gold-v2.pdf
Binary file not shown.
14 changes: 7 additions & 7 deletions docs/templates/v2-layered/using-templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,13 +234,13 @@ 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()` and
`SerifHeadline.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.
`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.

If the document's length is the author's rather than the template's,
pick a preset that paginates — `TimelineMinimal` splits its own columns
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
import com.demcha.examples.templates.coverletter.CvPanelLetterV2Example;
import com.demcha.examples.templates.coverletter.CvSidebarPortraitLetterV2Example;
import com.demcha.examples.templates.coverletter.CvTimelineMinimalLetterV2Example;
import com.demcha.examples.templates.cv.v2.CharcoalGoldExample;
import com.demcha.examples.templates.cv.v2.CvBlueBannerExample;
import com.demcha.examples.templates.cv.v2.CvBoxedV2Example;
import com.demcha.examples.templates.cv.v2.CvCenteredHeadlineExample;
Expand Down Expand Up @@ -151,6 +152,7 @@ public static void main(String[] args) throws Exception {
System.out.println("Generated: " + NavySidebarExample.generate());
System.out.println("Generated: " + ProfessionalSidebarExample.generate());
System.out.println("Generated: " + SerifHeadlineExample.generate());
System.out.println("Generated: " + CharcoalGoldExample.generate());

// Cover letters (v2 layered — 15 paired letters, one per CV preset)
System.out.println("Generated: " + CvBlueBannerLetterV2Example.generate());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
package com.demcha.examples.support;

import com.demcha.compose.document.image.DocumentImageData;
import com.demcha.compose.document.templates.core.identity.Contact;
import com.demcha.compose.document.templates.core.identity.Link;
import com.demcha.compose.document.templates.cv.data.CvDocument;
import com.demcha.compose.document.templates.cv.data.CvEntry;
import com.demcha.compose.document.templates.cv.data.CvIdentity;
import com.demcha.compose.document.templates.cv.data.CvSkill;
import com.demcha.compose.document.templates.cv.data.EntriesSection;
import com.demcha.compose.document.templates.cv.data.ParagraphSection;
import com.demcha.compose.document.templates.cv.data.RowStyle;
import com.demcha.compose.document.templates.cv.data.RowsSection;
import com.demcha.compose.document.templates.cv.data.SkillGroup;
import com.demcha.compose.document.templates.cv.data.SkillsSection;

import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.util.List;
import java.util.Objects;

/**
* Sample data for the Charcoal Gold CV example.
*
* <p>Sized to the design: the preset draws a fixed one-page sheet, and a CV
* much longer than this one does not compose at all — the two columns are a
* single atomic row, so it raises {@code AtomicNodeTooLargeException}.</p>
*
* <p>The credential marks are this preset's own vocabulary —
* {@code certificate}, {@code trophy}, {@code growth}, {@code star} — and
* each entry names the one it wants. The portrait is a neutral silhouette
* rather than a photograph, because the example ships in the repository; a
* real CV passes the candidate's own image the same way.</p>
*/
public final class CharcoalGoldSampleData {

private static final String NEWLINE = String.valueOf((char) 10);
private static final String PORTRAIT = "/cv-portrait-placeholder.png";

private CharcoalGoldSampleData() {
}

/**
* A delivery lead's one-page CV.
*
* @return the sample document
*/
public static CvDocument sample() {
return CvDocument.builder()
.identity(CvIdentity.builder()
.name("HELENA", "MARSH")
.jobTitle("DELIVERY LEAD")
.contact(new Contact("+44 161 496 0114",
"helena@example.com",
"Manchester, United Kingdom"))
.link(new Link("helenamarsh.co.uk", "https://helenamarsh.co.uk"))
.link(new Link("linkedin.com/in/hmarsh",
"https://www.linkedin.com/in/hmarsh"))
.portrait(portrait())
.build())
.section(new ParagraphSection("Summary", String.join(NEWLINE,
"Delivery lead with nine years running software programmes for"
+ " regulated clients, from discovery through the audit that"
+ " follows go-live.",
"Happiest with a small team, a short feedback loop, and a plan that"
+ " survives contact with the first week.")))
.section(SkillsSection.of("SKILLS", new SkillGroup("Core", List.of(
CvSkill.of("Delivery Management", 1.0),
CvSkill.of("Stakeholder Management", 1.0),
CvSkill.of("Risk & Assurance", 1.0),
CvSkill.of("Budget Ownership", 0.8),
CvSkill.of("Agile Coaching", 0.8),
CvSkill.of("Vendor Management", 0.8),
CvSkill.of("Roadmapping", 1.0),
CvSkill.of("Jira / Confluence", 1.0),
CvSkill.of("Power BI", 0.6),
CvSkill.of("Public Speaking", 0.6)))))
.section(RowsSection.builder("LANGUAGES", RowStyle.PLAIN)
.row("English", "Native")
.row("Welsh", "Native")
.row("Spanish", "B2 – Upper Intermediate")
.row("Italian", "A2 – Basic")
.build())
.section(EntriesSection.builder("EDUCATION")
.entry(CvEntry.builder("MSc Programme Management")
.subtitle("University of Manchester")
.date("2015 – 2016")
.build())
.entry(CvEntry.builder("BA Economics")
.subtitle("University of Leeds")
.date("2012 – 2015")
.build())
.build())
.section(EntriesSection.builder("EXPERIENCE")
.entry(CvEntry.builder("Delivery Lead")
.subtitle("NORTHWALL GROUP")
.place("MANCHESTER, UK")
.date("2021 – Present")
.link("https://northwall.example.com")
.body(List.of(
"Runs a portfolio of four programmes worth £8M against"
+ " a fixed regulatory deadline.",
"Rebuilt the intake process so a request is either"
+ " scheduled or refused within a week.",
"Coaches three delivery managers and chairs the monthly"
+ " assurance review.",
"Cut third-party spend by 18% by consolidating four"
+ " vendor contracts into one."))
.build())
.entry(CvEntry.builder("Senior Project Manager")
.subtitle("BRIDGEMOOR CONSULTING")
.place("LEEDS, UK")
.date("2018 – 2021")
.body(List.of(
"Delivered fourteen client programmes across finance"
+ " and public sector.",
"Owned budgets, timelines and the escalation path to"
+ " the client's board.",
"Introduced the risk register the practice still uses."))
.build())
.entry(CvEntry.builder("Project Manager")
.subtitle("ALDGATE DIGITAL")
.place("LONDON, UK")
.date("2016 – 2018")
.body(List.of(
"Ran delivery for two product teams and the platform"
+ " work beneath them.",
"Built the reporting pack the leadership team read"
+ " weekly."))
.build())
.build())
.section(EntriesSection.builder("CERTIFICATIONS")
.entry(CvEntry.builder("PMP® Certification")
.subtitle("Project Management Institute")
.date("2019")
.icon("certificate")
.build())
.entry(CvEntry.builder("PRINCE2® Practitioner")
.subtitle("AXELOS")
.date("2018")
.icon("certificate")
.build())
.entry(CvEntry.builder("Certified Scrum Professional")
.subtitle("Scrum Alliance")
.date("2017")
.icon("certificate")
.build())
.build())
.section(EntriesSection.builder("ACHIEVEMENTS")
.entry(CvEntry.builder("Programme of the Year")
.subtitle("Northwall Group")
.date("2024")
.icon("trophy")
.build())
.entry(CvEntry.builder("Intake Redesign")
.subtitle("Lead time down 62%")
.date("2023")
.icon("growth")
.build())
.entry(CvEntry.builder("Client Choice Award")
.subtitle("Bridgemoor Consulting")
.date("2020")
.icon("star")
.build())
.build())
.section(new ParagraphSection("TECHNICAL TOOLS", String.join(NEWLINE,
"Jira", "Confluence", "Miro", "Power BI", "Smartsheet", "Slack")))
.build();
}

/**
* The packaged silhouette that stands in for a photograph.
*
* @return the portrait image data
*/
private static DocumentImageData portrait() {
try (InputStream in = Objects.requireNonNull(
CharcoalGoldSampleData.class.getResourceAsStream(PORTRAIT),
"cv-portrait-placeholder.png missing from examples/src/main/resources/")) {
return DocumentImageData.fromBytes(in.readAllBytes());
} catch (IOException e) {
throw new UncheckedIOException("Failed to read the sample portrait", e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ record Entry(String title, String description, List<String> tags, String codeUrl
cv("cv-professional-sidebar-v2", "ProfessionalSidebarExample", "Professional Sidebar", "Navy monogram plate over a pale sidebar of contact marks, skill meters, an education rail and language ratings, beside a white column of profile, roles and projects.", "sidebar", "navy");
cv("cv-navy-sidebar-v2", "NavySidebarExample", "Navy Sidebar", "Navy plate with a ringed portrait, contact marks, degrees, skills and languages, beside a white column of summary, badged sections and roles strung on a timeline rail.", "sidebar", "navy", "portrait");
cv("cv-serif-headline-v2", "SerifHeadlineExample", "Serif Headline", "Volkhov masthead over a two-column body: roles on a timeline rail and marked project cards beside degrees and skill meters, closing with full-width certification and achievement bands.", "serif", "two-column");
cv("cv-charcoal-gold-v2", "CharcoalGoldExample", "Charcoal Gold", "Charcoal sidebar with a ringed photograph, rated skills and languages, beside a paper column with a two-tone name, a dated experience rail and paired credential columns.", "sidebar", "photo", "gold");

// ===== Templates / Cover Letter (v2 layered, paired 1:1 with CV) =====
// Registered directly: letter() points at the layered preset examples under
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
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.data.CvDocument;
import com.demcha.compose.document.templates.cv.presets.CharcoalGold;
import com.demcha.examples.support.CharcoalGoldSampleData;
import com.demcha.examples.support.ExampleOutputPaths;

import java.nio.file.Path;

/**
* Renders the layered {@code cv.v2} Charcoal Gold preset against the
* project-manager sample.
*
* <p>Output:
* {@code examples/target/generated-pdfs/templates/cv/cv-charcoal-gold-v2.pdf}.</p>
*
* <p>Unlike its ported siblings this preset leaves the page to the caller,
* so the session sets A4 with no margin: both columns run to the paper edge
* and each carries its own padding.</p>
*/
public final class CharcoalGoldExample {

private CharcoalGoldExample() {
}

/**
* @return absolute path of the rendered PDF
* @throws Exception if rendering fails
*/
public static Path generate() throws Exception {
Path outputFile = ExampleOutputPaths.prepare(
"templates/cv", "cv-charcoal-gold-v2.pdf");
CvDocument doc = CharcoalGoldSampleData.sample();
DocumentTemplate<CvDocument> template = CharcoalGold.create();

try (DocumentSession document = GraphCompose.document(outputFile)
.pageSize(DocumentPageSize.A4)
.margin(0f, 0f, 0f, 0f)
.create()) {
template.compose(document, doc);
document.buildPdf();
}
return outputFile;
}

/**
* @param args ignored
* @throws Exception if rendering fails
*/
public static void main(String[] args) throws Exception {
System.out.println("Generated: " + generate());
}
}
Loading
Loading