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

### Templates

- **A navy-plate CV preset: `MidnightNavy`.** A one-page sheet on a full-height navy
plate: an outlined monogram over a two-weight name and a tracked role line, then
contact, education, metered skills and dotted languages down the plate, beside a
paper column carrying the summary, the roles held on a rail, three achievement discs
and the certifications in divided columns. Ships as `cv.presets.MidnightNavy` on the
existing `CvDocument` model, porting the rendered layout of a published standalone
template; it needed no model change. The plate is a page background rather than a
section fill — it reaches three paper edges and a fill stops at its own box — sized
by the same ratio the body row splits on, so the two cannot drift apart. Every
horizontal pair goes through one wrapper: a row nested directly in a row cell is
refused and both columns are cells, so a skill and its meter, a language and its
dots, a title and its dates, a disc and its line and the certification columns are
each a row wrapped in a single layer of a stack. Three marks are relationships rather
than lengths: the experience rail is the entry section's left accent, so its height
derives from the entry and the inter-entry gap is padding inside the border, which is
what makes consecutive rails meet; a certification divider is the column's own
accent; and a skill meter is three layers sharing the track's axis rather than three
pieces placed apart. The monogram and the role line are built from the identity — two
initials and the name's own words — so a document fills neither in twice. Seven
berths reach their sections by title, and the contact heading is the preset's own
because a document has no section to carry it. Unlike its ported siblings this sheet
is one page **strictly**: the body is a single row and a row is atomic, so a longer CV
is refused with an `AtomicNodeTooLargeException` naming the node rather than being cut
— splitting the row would leave the plate on one page and half the aside on the next.
Two things depart from the ported sheet deliberately, and both are measured. A link is
drawn as its own label with the address behind it, as on the presets before it. And
every rail marker is centred on its rail: an accent is drawn centred on the edge it
belongs to, so the section's left edge already is the rail's axis, and the ported
sheet's extra half-thickness correction put each marker a rail width to the right of
the line it sits on — 0.72 pt, visible at reading size. Together the two come to
2 498 of 2 173 720 pixels, of which the centring is 200. Guarded by a smoke test (including the unknown-mark data error, the
monogram taken from the name, the uppercased name and tracked role, a language rating
rounded to the nearest of five dots, a skill with no level, a trunk prefix left
undialled, the link targets on every kind of title, a dropped berth, a document with
nothing but an identity, and the refusal past one page), an exact layout snapshot and
a pixel-parity gate; the examples showcase gains `cv-midnight-navy-v2`.

- **A two-column operations CV preset: `OrangeOps`.** A one-page sheet in three bands
over a split body: a two-tone name above a dark role bar whose right edge and three
accent slashes all lean by one ratio, a contact strip whose items sit on one axis
Expand Down
Binary file added assets/readme/examples/cv-midnight-navy-v2.pdf
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
import com.demcha.examples.templates.coverletter.CvTimelineMinimalLetterV2Example;
import com.demcha.examples.templates.cv.v2.CharcoalGoldExample;
import com.demcha.examples.templates.cv.v2.SlateOrangeExample;
import com.demcha.examples.templates.cv.v2.MidnightNavyExample;
import com.demcha.examples.templates.cv.v2.OrangeOpsExample;
import com.demcha.examples.templates.cv.v2.VioletGridExample;
import com.demcha.examples.templates.cv.v2.TealPulseExample;
Expand Down Expand Up @@ -164,6 +165,7 @@ public static void main(String[] args) throws Exception {
System.out.println("Generated: " + SlateOrangeExample.generate());
System.out.println("Generated: " + VioletGridExample.generate());
System.out.println("Generated: " + OrangeOpsExample.generate());
System.out.println("Generated: " + MidnightNavyExample.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,198 @@
package com.demcha.examples.support;

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.CvName;
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.SkillGroup;
import com.demcha.compose.document.templates.cv.data.SkillsSection;
import com.demcha.compose.document.templates.cv.data.Slot;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

/**
* Shared sample data for the Midnight Navy CV example.
*
* <p>Kept in lockstep with the qa module's {@code MidnightNavyFixtures} — the
* two modules cannot share a source file, so a content change here belongs
* there too.</p>
*/
public final class MidnightNavySampleData {

/** The break a body stacks its lines on. */
private static final String NEWLINE = String.valueOf((char) 10);

private MidnightNavySampleData() {
}

/**
* The canonical one-page CV — five contact rows including two links, two
* degrees, ten metered skills, three rated languages, the summary, three
* roles, three achievement discs and two certifications.
*
* @return the document
*/
public static CvDocument sample() {
List<CvDocument.Placement> placements = new ArrayList<>();
placements.add(new CvDocument.Placement(Slot.SIDEBAR, education()));
placements.add(new CvDocument.Placement(Slot.SIDEBAR, skills()));
placements.add(new CvDocument.Placement(Slot.SIDEBAR, languages()));
placements.add(new CvDocument.Placement(Slot.MAIN, summary()));
placements.add(new CvDocument.Placement(Slot.MAIN, experience()));
placements.add(new CvDocument.Placement(Slot.MAIN, achievements()));
placements.add(new CvDocument.Placement(Slot.MAIN, certifications()));
return new CvDocument(identity(), placements);
}

private static CvIdentity identity() {
return new CvIdentity(
CvName.of("Alex", "Morgan"),
"Marketing Manager",
new Contact("+1 (555) 123-4567",
"alex.morgan@email.com",
"New York, NY, USA"),
List.of(new Link("LinkedIn", "https://www.linkedin.com/in/alexmorgan"),
new Link("Portfolio", "https://alexmorgan.com")),
Optional.empty());
}

/** The degrees, newest first. */
private static EntriesSection education() {
return new EntriesSection("EDUCATION", List.of(
CvEntry.builder("MASTER OF BUSINESS ADMINISTRATION")
.subtitle("University of Chicago Booth School of Business")
.date("2016 \u2013 2018")
.build(),
CvEntry.builder("BACHELOR OF SCIENCE IN MARKETING")
.subtitle("Boston University")
.date("2012 \u2013 2016")
.build()));
}

/** The metered skills, as one flat group. */
private static SkillsSection skills() {
List<CvSkill> entries = new ArrayList<>();
entries.add(CvSkill.of("Strategic Planning", 0.82));
entries.add(CvSkill.of("Digital Marketing", 0.84));
entries.add(CvSkill.of("Market Research", 0.76));
entries.add(CvSkill.of("Brand Management", 0.84));
entries.add(CvSkill.of("Data Analysis", 0.81));
entries.add(CvSkill.of("Project Management", 0.68));
entries.add(CvSkill.of("Google Analytics", 0.83));
entries.add(CvSkill.of("SEO / SEM", 0.76));
entries.add(CvSkill.of("Microsoft Office Suite", 0.75));
entries.add(CvSkill.of("Communication", 0.96));
return new SkillsSection("SKILLS",
List.of(new SkillGroup("SKILLS", entries)));
}

/** The languages. The design shows a rating in fifths. */
private static SkillsSection languages() {
List<CvSkill> entries = new ArrayList<>();
entries.add(CvSkill.of("English", 1.0));
entries.add(CvSkill.of("Spanish", 0.8));
entries.add(CvSkill.of("French", 0.6));
return new SkillsSection("LANGUAGES",
List.of(new SkillGroup("LANGUAGES", entries)));
}

/** The opening prose. */
private static ParagraphSection summary() {
return new ParagraphSection("PROFESSIONAL SUMMARY",
"Results-driven Marketing Manager with 6+ years of "
+ "experience in developing and executing data-driven "
+ "marketing strategies that drive brand growth and "
+ "customer engagement. Proven track record in leading "
+ "cross-functional teams, managing successful campaigns, "
+ "and delivering measurable results. Passionate about "
+ "building strong brands and creating impactful marketing "
+ "initiatives.");
}

/** The roles held, each a headline over its bullets. */
private static EntriesSection experience() {
return new EntriesSection("EXPERIENCE", List.of(
CvEntry.builder("MARKETING MANAGER")
.subtitle("Starwave Solutions")
.place("New York, NY")
.date("2021 \u2013 Present")
.body(String.join(NEWLINE,
"Develop and implement comprehensive marketing strategies "
+ "that increased brand awareness by 40% and lead "
+ "generation by 35%.",
"Manage a team of 6 marketing professionals and "
+ "collaborate with sales, product, and design teams to "
+ "drive campaign success.",
"Oversee digital campaigns across SEO, SEM, social media, "
+ "and email marketing resulting in a 25% increase in ROI.",
"Analyze market trends and competitor activities to "
+ "identify new opportunities and optimize marketing "
+ "efforts."))
.build(),
CvEntry.builder("SENIOR MARKETING SPECIALIST")
.subtitle("BrightLine Technologies")
.place("New York, NY")
.date("2018 \u2013 2021")
.body(String.join(NEWLINE,
"Executed multi-channel marketing campaigns that improved "
+ "customer engagement by 30%.",
"Conducted market research and data analysis to support "
+ "strategic decision-making.",
"Managed social media channels and content strategy, "
+ "growing followers by 50%.",
"Coordinated with creative teams to develop compelling "
+ "content and visuals."))
.build(),
CvEntry.builder("MARKETING COORDINATOR")
.subtitle("Peak Performance Group")
.place("Boston, MA")
.date("2016 \u2013 2018")
.body(String.join(NEWLINE,
"Supported the development and execution of marketing "
+ "plans and campaigns.",
"Assisted in organizing events, webinars, and promotional "
+ "activities.",
"Monitored campaign performance and prepared reports for "
+ "management.",
"Maintained and updated marketing databases and CRM "
+ "systems."))
.build()));
}

/**
* The discs. A card has one line and no heading over it, so the line is
* the entry's title.
*/
private static EntriesSection achievements() {
return new EntriesSection("ACHIEVEMENTS", List.of(
CvEntry.builder("Increased brand awareness by 40% within 2 years.")
.icon("trophy")
.build(),
CvEntry.builder("Boosted lead generation by 35% annually.")
.icon("growth")
.build(),
CvEntry.builder("Recognized as Top Performer in 2022.")
.icon("award")
.build()));
}

/** The certifications, one to a column. */
private static EntriesSection certifications() {
return new EntriesSection("CERTIFICATIONS", List.of(
CvEntry.builder("Google Analytics Certified")
.subtitle("Google")
.date("2020")
.build(),
CvEntry.builder("HubSpot Content Marketing Certified")
.subtitle("HubSpot Academy")
.date("2021")
.build()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ record Entry(String title, String description, List<String> tags, String codeUrl
cv("cv-slate-orange-v2", "SlateOrangeExample", "Slate Orange", "Full-bleed slate masthead with an orange monogram tile, over a sidebar of marked competencies, trophied achievements and rated languages beside a profile, a dated experience rail and a credentials footer.", "sidebar", "masthead", "orange");
cv("cv-violet-grid-v2", "VioletGridExample", "Violet Grid", "Single-column sheet in bands: a two-tone name beside the contact list, a six-up grid of marked skills on dotted rules, a tools strip, a dated timeline, tinted project tiles and a closing quotation.", "single-column", "grid", "violet");
cv("cv-orange-ops-v2", "OrangeOpsExample", "Orange Ops", "Operations sheet with a two-tone name over a slanted role bar and accent slashes, a contact strip on hairlines, and a sidebar of skills, achievement discs and credentials beside a profile, dated roles and a four-metric strip.", "sidebar", "metrics", "orange");
cv("cv-midnight-navy-v2", "MidnightNavyExample", "Midnight Navy", "Full-height navy plate carrying an outlined monogram, a tracked role line, metered skills and dotted languages, beside a paper column with the summary, roles on a rail, achievement discs and divided certification columns.", "sidebar", "monogram", "navy");

// ===== 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,52 @@
package com.demcha.examples.templates.cv.v2;

import com.demcha.compose.GraphCompose;
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.MidnightNavy;
import com.demcha.examples.support.ExampleOutputPaths;
import com.demcha.examples.support.MidnightNavySampleData;

import java.nio.file.Path;

/**
* Renders the layered {@code cv.v2} Midnight Navy preset against the design
* sample.
*
* <p>Output:
* {@code examples/target/generated-pdfs/templates/cv/cv-midnight-navy-v2.pdf}.</p>
*
* <p>The preset owns its page geometry — every length is a share of the
* design's own grid, and the navy plate is a page background sized by the same
* ratio the body row splits on — so the session starts unconfigured.</p>
*/
public final class MidnightNavyExample {

private MidnightNavyExample() {
}

/**
* @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-midnight-navy-v2.pdf");
CvDocument doc = MidnightNavySampleData.sample();
DocumentTemplate<CvDocument> template = MidnightNavy.create();

try (DocumentSession document = GraphCompose.document(outputFile).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