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,49 @@
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.Panel;
import com.demcha.examples.support.ExampleDataFactory;
import com.demcha.examples.support.ExampleOutputPaths;

import java.nio.file.Path;

/**
* Renders the v2 Panel CV preset against the shared grouped skills
* sample data — pale-teal header card with centred Poppins masthead,
* full-width Profile panel, two-column row pairing Skills + Education
* on the left with Experience + Projects on the right, and a closing
* Additional panel.
*
* <p>Output:
* {@code examples/target/generated-pdfs/templates/cv/cv-panel-v2.pdf}.</p>
*/
public final class CvPanelExample {

private CvPanelExample() {
}

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

float m = (float) Panel.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());
}
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,23 @@ public static CvPalette editorialBlue() {
DocumentColor.rgb(193, 201, 211));
}

/**
* Panel palette ported from the v1 {@code PanelCvTemplateComposer}
* (ProductLeader tokens): body slate ink, slightly lighter slate
* for italic subtitles, the pale teal stroke used by every panel
* border, and the pale teal header card fill. The deeper header
* navy (rgb(20,44,66)), teal accent (rgb(0,128,128)), and white
* panel fill are preset-local because they are the fifth/sixth/
* seventh tokens — other v2 presets do not share them today.
*/
public static CvPalette panel() {
return new CvPalette(
DocumentColor.rgb(54, 68, 84), // ink — V1 BODY_TEXT/HEADER_META slate
DocumentColor.rgb(105, 117, 132), // muted — slightly lighter slate
DocumentColor.rgb(179, 214, 211), // rule — V1 PANEL_STROKE pale teal
DocumentColor.rgb(231, 246, 244)); // banner — V1 HEADER_FILL pale teal
}

/**
* Executive palette ported from the v1 {@code ExecutiveSlateCvTemplate}:
* mid-slate body ink, soft muted slate for italic subtitles, the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,30 @@ public static CvSpacing editorialBlue() {
3.0); // entrySeparation
}

/**
* Spacing for the Panel preset: card-led layout that has to fit
* Header / Profile / two-column row / Additional on one A4 page,
* so paddings and inter-card gaps are tight by design. Corner
* radius and accent rule width match the V1 ProductLeader tokens.
*/
public static CvSpacing panel() {
return new CvSpacing(
6, // pageFlowSpacing (tight inter-card gap)
3, // sectionBodySpacing (inside a card)
DocumentInsets.zero(), // sectionBodyPadding (the card supplies its own padding)
DocumentInsets.zero(), // headlinePadding
DocumentInsets.zero(), // contactPadding
7.0, // bannerCornerRadius (V1 CORNER_RADIUS)
8.0, // bannerInnerPadding (compact card padding)
DocumentInsets.zero(), // bannerMargin
2.2, // accentRuleWidth (V1 ACCENT_HEIGHT)
1.0, // paragraphMarginTop
8.0, // entryHeaderRowSpacing
1.0, // entryTitleWeight
0.45, // entryDateWeight
2.0); // entrySeparation
}

/**
* Spacing for the Executive preset: generous executive feel with
* an 8pt page-flow rhythm, compact module bodies, and a 1.1pt
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,21 @@ public static CvTheme editorialBlue() {
CvDecoration.classic());
}

/**
* The "Panel" look — Poppins headlines + Lato body, pale teal
* header card and module panels with thin teal stroke, deep navy
* masthead text, and teal section headings with a small accent
* strip beneath each title. Visual signature ported from the v1
* {@code PanelCvTemplateComposer} (ProductLeader tokens).
*/
public static CvTheme panel() {
return new CvTheme(
CvPalette.panel(),
CvTypography.panel(),
CvSpacing.panel(),
CvDecoration.classic());
}

/**
* The "Executive" look — Poppins masthead + Lato body, deep slate
* primary, warm bronze accent on module headings and contact
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,26 @@ public static CvTypography editorialBlue() {
1.45); // line spacing
}

/**
* Poppins headline + Lato body scale ported from the v1
* {@code PanelCvTemplateComposer} (ProductLeader tokens): a 22pt
* uppercase name in the tinted header card, a 10.4pt section-title
* slot for the teal module headings, and a 9.4pt body with 1.2
* line spacing tuned for the dense card layout.
*/
public static CvTypography panel() {
return new CvTypography(
FontName.POPPINS, FontName.LATO,
22.0, // headline (centered uppercase name)
8.9, // contact (V1 META_SIZE = body - 0.5)
10.4, // banner / module title (V1 SECTION_SIZE)
9.4, // entry title
9.4, // entry date
9.0, // entry subtitle (italic)
9.4, // body (V1 BODY_SIZE)
1.2); // line spacing
}

/**
* Poppins headline + Lato body scale ported from the v1
* {@code ExecutiveSlateCvTemplate}: a 24pt uppercase masthead, a
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.demcha.compose.document.templates.widgets;

import com.demcha.compose.document.dsl.PageFlowBuilder;
import com.demcha.compose.document.dsl.SectionBuilder;
import com.demcha.compose.document.style.DocumentColor;
import com.demcha.compose.document.style.DocumentCornerRadius;
Expand Down Expand Up @@ -31,21 +32,47 @@ public static void render(SectionBuilder parent,
Style safeStyle = style == null ? Style.builder().build() : style;

parent.addSection(name, card -> {
card.spacing(safeStyle.spacing())
.padding(safeStyle.padding());
if (safeStyle.fillColor() != null) {
card.fillColor(safeStyle.fillColor());
}
if (safeStyle.stroke() != null) {
card.stroke(safeStyle.stroke());
}
if (safeStyle.cornerRadius() != null) {
card.cornerRadius(safeStyle.cornerRadius());
}
applyStyle(card, safeStyle);
content.accept(card);
});
}

/**
* Top-level overload — renders the card as a page-flow section so
* presets can place full-width cards directly under
* {@link PageFlowBuilder} without wrapping them in a parent
* section. Visual shell behaves identically to the
* {@link #render(SectionBuilder, String, Style, Consumer)}
* variant.
*/
public static void render(PageFlowBuilder flow,
String name,
Style style,
Consumer<SectionBuilder> content) {
Objects.requireNonNull(flow, "flow");
Objects.requireNonNull(content, "content");
Style safeStyle = style == null ? Style.builder().build() : style;

flow.addSection(name, card -> {
applyStyle(card, safeStyle);
content.accept(card);
});
}

private static void applyStyle(SectionBuilder card, Style style) {
card.spacing(style.spacing())
.padding(style.padding());
if (style.fillColor() != null) {
card.fillColor(style.fillColor());
}
if (style.stroke() != null) {
card.stroke(style.stroke());
}
if (style.cornerRadius() != null) {
card.cornerRadius(style.cornerRadius());
}
}

/**
* Visual shell options for {@link CardWidget}.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,10 @@ private static Stream<Arguments> presets() {
(Supplier<DocumentTemplate<CvDocument>>) CompactMono::create),
Arguments.of("executive",
Executive.RECOMMENDED_MARGIN,
(Supplier<DocumentTemplate<CvDocument>>) Executive::create));
(Supplier<DocumentTemplate<CvDocument>>) Executive::create),
Arguments.of("panel",
Panel.RECOMMENDED_MARGIN,
(Supplier<DocumentTemplate<CvDocument>>) Panel::create));
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package com.demcha.compose.document.templates.cv.v2.presets;

import com.demcha.compose.GraphCompose;
import com.demcha.compose.document.api.DocumentSession;
import com.demcha.compose.document.style.DocumentInsets;
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.data.CvIdentity;
import com.demcha.compose.document.templates.cv.v2.data.EntriesSection;
import com.demcha.compose.document.templates.cv.v2.data.ParagraphSection;
import com.demcha.compose.document.templates.cv.v2.data.RowStyle;
import com.demcha.compose.document.templates.cv.v2.data.RowsSection;
import com.demcha.compose.document.templates.cv.v2.data.SkillsSection;
import com.demcha.compose.document.templates.cv.v2.theme.CvTheme;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

/**
* Smoke test for the v2 Panel preset. Covers the header card with
* optional job title + link row, plus the two-column row composition
* fed through {@link com.demcha.compose.document.templates.cv.v2.components.SectionLookup}
* and {@link com.demcha.compose.document.templates.cv.v2.components.SectionDispatcher}.
*/
class PanelSmokeTest {

@Test
void exposes_stable_identity() {
DocumentTemplate<CvDocument> template = Panel.create();
assertThat(template.id()).isEqualTo("panel");
assertThat(template.displayName()).isEqualTo("Panel");
}

@Test
void default_factory_renders_full_document() throws Exception {
renderAndAssertNonEmpty(Panel.create(), fullDocument());
}

@Test
void custom_theme_factory_renders() throws Exception {
renderAndAssertNonEmpty(Panel.create(CvTheme.panel()), fullDocument());
}

private static void renderAndAssertNonEmpty(
DocumentTemplate<CvDocument> template,
CvDocument doc) throws Exception {
try (DocumentSession session = GraphCompose.document()
.pageSize(420, 595)
.margin(DocumentInsets.of(18))
.create()) {
template.compose(session, doc);
assertThat(session.roots()).isNotEmpty();
}
}

private static CvDocument fullDocument() {
return CvDocument.builder()
.identity(CvIdentity.builder()
.name("Jane", "Doe")
.jobTitle("Product Lead")
.contact("+44 0", "j@d.com", "London")
.link("LinkedIn", "https://linkedin.com/in/jane-doe")
.build())
.sections(
new ParagraphSection("Professional Summary",
"Builds **reliable** product platforms."),
SkillsSection.builder("Technical Skills")
.group("Languages", "Java 21", "Kotlin")
.group("Testing", "JUnit 5", "AssertJ")
.build(),
EntriesSection.builder("Education & Certifications")
.entry("MSc Computer Science",
"University of Manchester",
"2019-2021",
"Distinction.")
.build(),
RowsSection.builder("Projects", RowStyle.BULLETED_STACKED)
.row("GraphCompose (Java, PDFBox)",
"Declarative PDF layout engine.")
.build(),
EntriesSection.builder("Professional Experience")
.entry("Lead", "Acme", "2021-2024",
"Built rendering services.")
.build(),
RowsSection.builder("Additional Information", RowStyle.PLAIN)
.row("Languages", "English, German")
.build())
.build();
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.