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
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
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.v2.data.CvDocument;
import com.demcha.compose.document.templates.cv.v2.presets.ModernProfessional;
import com.demcha.examples.support.ExampleDataFactory;
import com.demcha.examples.support.ExampleOutputPaths;

import java.nio.file.Path;

/**
* Renders the v2 Modern Professional CV preset against the shared
* sample data — single-page, right-aligned slate-blue name, flat
* bright-blue section titles.
*
* <p>Output:
* {@code examples/target/generated-pdfs/templates/cv/cv-modern-professional-v2.pdf}.</p>
*/
public final class CvModernV2Example {

private CvModernV2Example() {
}

public static Path generate() throws Exception {
Path outputFile = ExampleOutputPaths.prepare(
"templates/cv", "cv-modern-professional-v2.pdf");
CvDocument doc = ExampleDataFactory.sampleCvDocumentV2();
DocumentTemplate<CvDocument> template = ModernProfessional.create();

float m = (float) ModernProfessional.RECOMMENDED_MARGIN;
try (DocumentSession document = GraphCompose.document(outputFile)
.pageSize(DocumentPageSize.A4)
.margin(m, m, m, m)
.create()) {
template.compose(document, doc);
document.buildPdf();
}
return outputFile;
}

public static void main(String[] args) throws Exception {
System.out.println("Generated: " + generate());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
package com.demcha.compose.document.templates.cv.v2.presets;

import com.demcha.compose.document.api.DocumentSession;
import com.demcha.compose.document.dsl.PageFlowBuilder;
import com.demcha.compose.document.dsl.SectionBuilder;
import com.demcha.compose.document.node.DocumentLinkOptions;
import com.demcha.compose.document.node.TextAlign;
import com.demcha.compose.document.style.DocumentColor;
import com.demcha.compose.document.style.DocumentInsets;
import com.demcha.compose.document.style.DocumentTextDecoration;
import com.demcha.compose.document.style.DocumentTextStyle;
import com.demcha.compose.document.templates.api.DocumentTemplate;
import com.demcha.compose.document.templates.cv.v2.components.SectionDispatcher;
import com.demcha.compose.document.templates.cv.v2.data.CvContact;
import com.demcha.compose.document.templates.cv.v2.data.CvDocument;
import com.demcha.compose.document.templates.cv.v2.data.CvIdentity;
import com.demcha.compose.document.templates.cv.v2.data.CvLink;
import com.demcha.compose.document.templates.cv.v2.data.CvSection;
import com.demcha.compose.document.templates.cv.v2.data.Slot;
import com.demcha.compose.document.templates.cv.v2.theme.CvTheme;
import com.demcha.compose.font.FontName;

import java.util.List;
import java.util.Objects;

/**
* v2 port of the canonical "Modern Professional" CV preset.
*
* <p>Visual signature ported from the legacy v1 preset:</p>
* <ul>
* <li>Right-aligned <strong>big slate-blue display name</strong>
* (no spaced caps, no centring) at the top.</li>
* <li>Right-aligned pipe-separated contact + link row beneath.</li>
* <li>Large <strong>bright-blue bold section titles</strong> —
* flat, left-aligned, no banner panel.</li>
* <li>Body text in Helvetica 10pt — denser than Boxed Sections.</li>
* <li>Single-page-friendly proportions on A4 with 18pt margins.</li>
* </ul>
*
* <p><strong>Why some colours live inside this preset and not in
* {@link CvTheme}:</strong> the slate-blue display name and the
* bright-blue accent for section titles are unique to this preset —
* no other v2 preset shares them today. Putting them in
* {@link com.demcha.compose.document.templates.cv.v2.theme.CvPalette}
* would pollute the palette with single-use fields. When (or if) a
* second preset reaches for the same colours, extract them to
* {@code CvPalette} and update both presets.</p>
*
* <p><strong>Architectural lesson learned in Phase 2:</strong>
* single-column presets that don't fit the boxed-banner visual
* (e.g. flat titles, underlined titles, coloured titles) currently
* inline their own {@code renderSectionTitle} helper. Once 3+ presets
* share this need, factor out a {@code SectionTitleRenderer}
* component with style variants. Until then, the per-preset inline
* helper keeps each preset readable end-to-end.</p>
*/
public final class ModernProfessional {

/** Stable template identifier. */
public static final String ID = "modern-professional";

/** Human-readable display name. */
public static final String DISPLAY_NAME = "Modern Professional";

/** Recommended page margin (in points) — matches the legacy v1 preset. */
public static final double RECOMMENDED_MARGIN = 18.0;

/** Slate-blue used by the display name. Preset-specific. */
private static final DocumentColor NAME_COLOR = DocumentColor.rgb(44, 62, 80);

/** Bright-blue used by section titles. Preset-specific. */
private static final DocumentColor SECTION_TITLE_COLOR =
DocumentColor.rgb(41, 128, 185);

/** Royal-blue used by contact links. Preset-specific. */
private static final DocumentColor LINK_COLOR = DocumentColor.rgb(65, 105, 225);

private ModernProfessional() {
}

/**
* Builds the preset with the Modern Professional theme
* ({@link CvTheme#modernProfessional()}).
*/
public static DocumentTemplate<CvDocument> create() {
return create(CvTheme.modernProfessional());
}

/**
* Builds the preset with a caller-supplied theme. Allows
* variations on the Modern Professional theme (different
* typography scale, custom spacing) without forking this class.
*/
public static DocumentTemplate<CvDocument> create(CvTheme theme) {
Objects.requireNonNull(theme, "theme");
return new Template(theme);
}

private static final class Template implements DocumentTemplate<CvDocument> {

private final CvTheme theme;

Template(CvTheme theme) {
this.theme = theme;
}

@Override
public String id() {
return ID;
}

@Override
public String displayName() {
return DISPLAY_NAME;
}

@Override
public void compose(DocumentSession document, CvDocument doc) {
Objects.requireNonNull(document, "document");
Objects.requireNonNull(doc, "doc");

PageFlowBuilder pageFlow = document.dsl()
.pageFlow()
.name("CvV2ModernRoot")
.spacing(theme.spacing().pageFlowSpacing())
.addSection("Header", section ->
renderHeader(section, doc.identity()))
.addSection("Contact", section ->
renderContact(section, doc.identity()));

// Single-column preset — only MAIN slot.
List<CvSection> sections = doc.sectionsIn(Slot.MAIN);
for (int i = 0; i < sections.size(); i++) {
final CvSection sec = sections.get(i);
final int idx = i;
pageFlow.addSection("Title_" + idx, host ->
renderSectionTitle(host, sec.title()));
pageFlow.addSection("Body_" + idx, host ->
SectionDispatcher.renderBody(host, sec, theme));
}

pageFlow.build();
}

/**
* Big slate-blue display name, right-aligned. No spaced caps.
*/
private void renderHeader(SectionBuilder section, CvIdentity identity) {
DocumentTextStyle nameStyle = DocumentTextStyle.builder()
.fontName(FontName.HELVETICA_BOLD)
.size(theme.typography().sizeHeadline())
.decoration(DocumentTextDecoration.BOLD)
.color(NAME_COLOR)
.build();

section.padding(DocumentInsets.zero())
.addParagraph(p -> p
.text(identity.name().full())
.textStyle(nameStyle)
.align(TextAlign.RIGHT)
.margin(DocumentInsets.zero()));
}

/**
* Right-aligned pipe-separated contact + links. Links rendered
* underlined in royal blue; contact strings in body grey.
*/
private void renderContact(SectionBuilder section, CvIdentity identity) {
DocumentTextStyle bodyStyle = DocumentTextStyle.builder()
.fontName(FontName.HELVETICA)
.size(theme.typography().sizeContact())
.color(theme.palette().ink())
.build();
DocumentTextStyle linkStyle = DocumentTextStyle.builder()
.fontName(FontName.HELVETICA)
.size(theme.typography().sizeContact())
.decoration(DocumentTextDecoration.UNDERLINE)
.color(LINK_COLOR)
.build();
DocumentTextStyle separatorStyle = DocumentTextStyle.builder()
.fontName(FontName.HELVETICA)
.size(theme.typography().sizeContact())
.color(theme.palette().rule())
.build();

CvContact c = identity.contact();
section.padding(theme.spacing().contactPadding())
.accentBottom(theme.palette().rule(),
theme.spacing().accentRuleWidth())
.addParagraph(p -> p
.textStyle(bodyStyle)
.align(TextAlign.RIGHT)
.margin(DocumentInsets.zero())
.rich(rich -> {
rich.style(c.address(), bodyStyle);
rich.style(" | ", separatorStyle);
rich.style(c.phone(), bodyStyle);
rich.style(" | ", separatorStyle);
rich.link(c.email(),
new DocumentLinkOptions("mailto:" + c.email()));
for (CvLink l : identity.links()) {
rich.style(" | ", separatorStyle);
rich.style(l.label(), linkStyle);
}
}));
}

/**
* Flat bright-blue bold section title, left-aligned, no panel.
* This is the visual hallmark of the Modern Professional look.
*/
private void renderSectionTitle(SectionBuilder section, String title) {
DocumentTextStyle titleStyle = DocumentTextStyle.builder()
.fontName(FontName.HELVETICA_BOLD)
.size(theme.typography().sizeBanner())
.decoration(DocumentTextDecoration.BOLD)
.color(SECTION_TITLE_COLOR)
.build();

section.padding(new DocumentInsets(8, 0, 2, 0))
.addParagraph(p -> p
.text(title)
.textStyle(titleStyle)
.align(TextAlign.LEFT)
.margin(DocumentInsets.zero()));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,28 @@ public static CvSpacing classic() {
1.0, // entryTitleWeight
0.45); // entryDateWeight
}

/**
* Tighter spacing for the Modern Professional preset — no banner
* panels, denser body, single-page-friendly proportions.
* Banner-related fields (corner radius, inner padding, margin)
* are left non-zero so a future preset that wants to draw an MP
* banner can read them; the canonical MP preset ignores them.
*/
public static CvSpacing modernProfessional() {
return new CvSpacing(
4, // pageFlowSpacing
3, // sectionBodySpacing
new DocumentInsets(2, 0, 0, 0), // sectionBodyPadding
new DocumentInsets(0, 0, 0, 0), // headlinePadding
new DocumentInsets(0, 0, 6, 0), // contactPadding
0.0, // bannerCornerRadius (unused)
5.0, // bannerInnerPadding (unused)
DocumentInsets.top(6), // bannerMargin (unused — section title margin)
0.7, // accentRuleWidth
2.0, // paragraphMarginTop
10.0, // entryHeaderRowSpacing
1.0, // entryTitleWeight
0.45); // entryDateWeight
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,25 @@ public static CvTheme boxedClassic() {
CvDecoration.classic());
}

/**
* The "Modern Professional" look — Helvetica throughout, larger
* scale, tighter spacing. Body palette is the classic ink/muted
* pair; the preset itself adds the slate-blue name and
* bright-blue section title accents because those colours are not
* shared with any other v2 preset today.
*
* <p>When (or if) a second preset wants the same accent palette,
* extract those colours into a new field on {@link CvPalette} and
* point both presets at it.</p>
*/
public static CvTheme modernProfessional() {
return new CvTheme(
CvPalette.classic(),
CvTypography.modernProfessional(),
CvSpacing.modernProfessional(),
CvDecoration.classic());
}

// -- pre-built text-style helpers ------------------------------------
// Renderers ask the theme for an already-composed DocumentTextStyle
// instead of re-assembling font + size + decoration + colour every
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,21 @@ public static CvTypography classic() {
8.6, // body
1.4); // line spacing
}

/**
* Helvetica scale for the Modern Professional preset — larger
* display name, larger section titles, comfortable body size.
*/
public static CvTypography modernProfessional() {
return new CvTypography(
FontName.HELVETICA_BOLD, FontName.HELVETICA,
28.0, // headline (display name)
9.0, // contact
17.4, // banner (used as section title here)
10.5, // entry title
10.0, // entry date
9.5, // entry subtitle
10.0, // body
1.35); // line spacing
}
}
Loading