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

### Templates

- **An architect's two-column CV preset: `TerracottaRail`.** A one-page 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
rail, a projects grid and the degrees. Ships as `cv.presets.TerracottaRail` on the
existing `CvDocument` model with **no model change at all**, porting the rendered
layout of a published standalone template. Like `CharcoalGold` it leaves the page to
the caller. Eight berths reach their sections by title; the two bulleted lists are
skills without levels, because this design writes them as plain lines — one takes a
terracotta square and no dash under its heading, the other a disc and a dash, which
is how the sheet tells two lists of one-liners apart. Each fact and each project
takes the mark its entry names in `CvEntry.icon()` from this preset's own vocabulary,
a project title is a link when its entry carries one, and the monogram is drawn from
the name's own initials rather than a field of its own — a document states its name
once, and a monogram that could disagree with it would be a second place to keep
true. A link in the contact block is set smaller than the channels above it, because
a URL is one long token that cannot be broken and is the line that outgrows that
column first; it 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 and the one-page limit), 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
on a terracotta block at its head, a tinted quarter-disc, an arch and a sprig running
Expand Down
Binary file added assets/readme/examples/cv-terracotta-rail-v2.pdf
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
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.TerracottaRailExample;
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 @@ -154,6 +155,7 @@ public static void main(String[] args) throws Exception {
System.out.println("Generated: " + ProfessionalSidebarExample.generate());
System.out.println("Generated: " + SerifHeadlineExample.generate());
System.out.println("Generated: " + CharcoalGoldExample.generate());
System.out.println("Generated: " + TerracottaRailExample.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
Expand Up @@ -64,6 +64,7 @@ record Entry(String title, String description, List<String> tags, String codeUrl
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");
cv("cv-terracotta-rail-v2", "TerracottaRailExample", "Terracotta Rail", "Serif monogram over a terracotta rule, contact channels behind their marks and two bulleted lists, beside a letter-spaced masthead, roles on a ringed rail, a projects grid and the degrees.", "sidebar", "monogram", "terracotta");

// ===== 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,207 @@
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;
import java.util.OptionalDouble;

/**
* Shared sample data for the Terracotta Rail CV example.
*
* <p>The sample is the preset's reference content: an architect's one-page
* CV with a monogram, four contact rows including a link, two bulleted
* lists, two credentials, three facts behind their marks, a three-paragraph
* summary, three roles on the rail, three marked projects and two
* degrees.</p>
*
* <p>Kept in lockstep with the qa module's {@code TerracottaRailFixtures} —
* the two modules cannot share a source file, so a content change here
* belongs there too.</p>
*/
public final class TerracottaRailSampleData {

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

/** The dash this sheet writes a date range with. */
private static final String EN_DASH = String.valueOf((char) 0x2013);

private TerracottaRailSampleData() {
}

/** The single-page reference CV. */
public static CvDocument sample() {
List<CvDocument.Placement> placements = new ArrayList<>();
placements.add(new CvDocument.Placement(Slot.SIDEBAR, competencies()));
placements.add(new CvDocument.Placement(Slot.SIDEBAR, software()));
placements.add(new CvDocument.Placement(Slot.SIDEBAR, certifications()));
placements.add(new CvDocument.Placement(Slot.SIDEBAR, facts()));
placements.add(new CvDocument.Placement(Slot.MAIN, summary()));
placements.add(new CvDocument.Placement(Slot.MAIN, experience()));
placements.add(new CvDocument.Placement(Slot.MAIN, projects()));
placements.add(new CvDocument.Placement(Slot.MAIN, education()));
return new CvDocument(identity(), placements);
}

private static CvIdentity identity() {
return new CvIdentity(
CvName.of("OLIVER", "BENNETT"),
"SENIOR ARCHITECT",
new Contact("+44 7700 900123", "oliver.bennett@email.com",
"Bristol, United Kingdom"),
List.of(new Link("linkedin.com/in/oliverbennett-architect",
"https://linkedin.com/in/oliverbennett-architect")),
Optional.empty());
}

private static SkillsSection competencies() {
return plainSkills("CORE COMPETENCIES",
"Architectural Design", "Concept Development", "Planning Applications",
"Technical Drawings", "Revit", "AutoCAD", "BIM Coordination",
"Design Presentations", "Project Delivery", "Stakeholder Management",
"Sustainable Design", "Building Regulations");
}

private static SkillsSection software() {
return plainSkills("SOFTWARE",
"Revit", "AutoCAD", "Rhino", "SketchUp", "Adobe InDesign", "Photoshop",
"Microsoft Office");
}

private static EntriesSection certifications() {
return new EntriesSection("CERTIFICATIONS", List.of(
CvEntry.builder("ARB Registered Architect").build(),
CvEntry.builder("RIBA Chartered Member").build()));
}

private static EntriesSection facts() {
return new EntriesSection("ADDITIONAL INFORMATION", List.of(
CvEntry.builder("Languages:")
.icon("globe")
.body("English (Native)," + NEWLINE + "Spanish (Conversational)")
.build(),
CvEntry.builder("Right to Work:")
.icon("badge")
.body("United Kingdom")
.build(),
CvEntry.builder("Availability:")
.icon("clock")
.body("1 month notice")
.build()));
}

private static ParagraphSection summary() {
return new ParagraphSection("PROFESSIONAL SUMMARY", String.join(NEWLINE,
"Senior Architect with over 8 years of experience delivering residential, "
+ "mixed-use, and commercial projects across all RIBA work stages.",
"Proven expertise in design development, planning applications, and technical "
+ "delivery with a strong focus on sustainability, quality, and user "
+ "experience.",
"Collaborative communicator skilled in coordinating consultants, engaging "
+ "stakeholders, and leading multidisciplinary teams to achieve "
+ "successful outcomes."));
}

private static EntriesSection experience() {
return new EntriesSection("PROFESSIONAL EXPERIENCE", List.of(
CvEntry.builder("Senior Architect")
.subtitle("Northline Studio, Bristol, UK")
.date("2021" + EN_DASH + "Present")
.body(String.join(NEWLINE,
"Lead design packages from concept through to detailed design "
+ "across mid-scale residential and mixed-use projects.",
"Coordinate structural, M&E, landscape and planning consultants "
+ "to ensure integrated and buildable solutions.",
"Prepare and manage planning applications, design and access "
+ "statements, and supporting documentation.",
"Present and communicate design proposals to clients and "
+ "stakeholders, securing approvals and driving projects "
+ "forward."))
.build(),
CvEntry.builder("Architect")
.subtitle("Urban Form Partners, Bath, UK")
.date("2018" + EN_DASH + "2021")
.body(String.join(NEWLINE,
"Developed concept and technical designs for a range of "
+ "residential, commercial, and education projects.",
"Prepared coordinated drawing sets, schedules, and "
+ "specifications to support planning and construction.",
"Supported project delivery on site, resolving design queries "
+ "and ensuring quality outcomes.",
"Coordinated with contractors and consultants to maintain "
+ "programme, budget and design intent."))
.build(),
CvEntry.builder("Part II Architectural Assistant")
.subtitle("Axis Design Workshop, Cardiff, UK")
.date("2016" + EN_DASH + "2018")
.body(String.join(NEWLINE,
"Supported senior architects across all RIBA stages on a "
+ "variety of project types.",
"Produced CAD and Revit drawings, models, and visualisations to "
+ "communicate design intent.",
"Undertook research, prepared presentation boards, and assisted "
+ "with planning documentation."))
.build()));
}

private static EntriesSection projects() {
return new EntriesSection("SELECTED PROJECTS", List.of(
CvEntry.builder("Harbour Point")
.subtitle("Mixed-Use Regeneration")
.place("Bristol")
.icon("building")
.body("Mixed-use development delivering 142 homes, retail space and "
+ "public realm improvements; secured planning consent and is "
+ "now on site.")
.build(),
CvEntry.builder("The Assembly Hotel")
.subtitle("Boutique Hotel Refurbishment")
.place("Bath")
.icon("hotel")
.body("Sensitive refurbishment of listed building to create a "
+ "36-bedroom boutique hotel; enhanced heritage features and "
+ "guest experience.")
.build(),
CvEntry.builder("Elmwood Mews")
.subtitle("Residential Development")
.place("Clifton, Bristol")
.icon("house")
.body("Design of 28 high-quality homes within a sustainable masterplan; "
+ "achieved high environmental standards and strong sales "
+ "performance.")
.build()));
}

private static EntriesSection education() {
return new EntriesSection("EDUCATION", List.of(
CvEntry.builder("MArch Architecture")
.subtitle("University of Sheffield")
.date("2014" + EN_DASH + "2016")
.build(),
CvEntry.builder("BA (Hons) Architecture")
.subtitle("University of the West of England")
.date("2011" + EN_DASH + "2014")
.build()));
}

private static SkillsSection plainSkills(String title, String... names) {
List<CvSkill> entries = new ArrayList<>();
for (String name : names) {
entries.add(new CvSkill(name, OptionalDouble.empty()));
}
return new SkillsSection(title, List.of(new SkillGroup(title, entries)));
}
}
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.TerracottaRail;
import com.demcha.examples.support.ExampleOutputPaths;
import com.demcha.examples.support.TerracottaRailSampleData;

import java.nio.file.Path;

/**
* Renders the layered {@code cv.v2} Terracotta Rail preset against the
* architect sample.
*
* <p>Output:
* {@code examples/target/generated-pdfs/templates/cv/cv-terracotta-rail-v2.pdf}.</p>
*
* <p>The 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 TerracottaRailExample {

private TerracottaRailExample() {
}

/**
* @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-terracotta-rail-v2.pdf");
CvDocument doc = TerracottaRailSampleData.sample();
DocumentTemplate<CvDocument> template = TerracottaRail.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