Skip to content
Merged
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
Expand Up @@ -4,17 +4,23 @@
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.data.*;
import com.demcha.compose.document.templates.cv.v2.theme.CvTheme;
import com.demcha.compose.document.templates.cv.v2.theme.CvTypography;
import com.demcha.compose.font.FontFamilyDefinition;
import com.demcha.compose.font.FontName;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper;
import org.junit.jupiter.api.Test;

import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assumptions.assumeTrue;

/**
* Smoke test for the v2 Engineering Resume preset. Covers the navy
Expand All @@ -24,6 +30,17 @@
*/
class EngineeringResumeSmokeTest {

// this flag exists to allow us to emit the PDF if we want to exmine it visually.
private static final boolean SAVE_MULTILINGUAL_PDF = true;
private static final FontName MULTISCRIPT_FONT = FontName.of("Test Multiscript");
// Hungarian sample: classic accented-Latin unicode test phrase,
// often glossed as "floodproof mirror-drilling machine".
private static final String HUNGARIAN_TEXT = "Árvíztűrő tükörfúrógép";
// Hebrew sample: "shalom olam" / "hello world".
private static final String HEBREW_TEXT = "שלום עולם";
// Arabic sample: "marhaban bil-alam" / "hello world".
private static final String ARABIC_TEXT = "مرحبا بالعالم";

@Test
void exposes_stable_identity() {
DocumentTemplate<CvDocument> template = EngineeringResume.create();
Expand All @@ -42,6 +59,46 @@ void custom_theme_factory_renders() throws Exception {
fullDocument());
}

@Test
void custom_multiscript_theme_renders_hungarian_hebrew_and_arabic_to_pdf() throws Exception {
Path fontPath = resolveMultiscriptFont();
assumeTrue(fontPath != null, "requires a local font with Hebrew + Arabic + Latin coverage");

DocumentTemplate<CvDocument> template = EngineeringResume.create(multiscriptTheme());

try (DocumentSession session = GraphCompose.document()
.pageSize(420, 595)
.margin(DocumentInsets.of(20))
.create()) {
session.registerFontFamily(FontFamilyDefinition.files(MULTISCRIPT_FONT, fontPath).build());
template.compose(session, multilingualDocument());

byte[] pdfBytes = session.toPdfBytes();
maybeWriteMultilingualPdf(pdfBytes);
assertThat(pdfBytes).hasSizeGreaterThan(500);
assertThat(new String(pdfBytes, 0, 5, StandardCharsets.US_ASCII)).isEqualTo("%PDF-");

try (PDDocument document = Loader.loadPDF(pdfBytes)) {
assertThat(document.getNumberOfPages()).isGreaterThan(0);

String extracted = new PDFTextStripper().getText(document);
// Hungarian is the LTR Unicode control in this test: it proves
// the preset can carry non-English text through a real resume
// render without introducing RTL ordering/shaping ambiguity.
assertThat(extracted).contains(HUNGARIAN_TEXT);

// We intentionally do not assert exact PDFTextStripper output
// for Hebrew or Arabic. PDF text extraction is heuristic and
// may reorder or normalize RTL runs independently of whether
// the renderer successfully painted the correct glyphs.
//
// The thing being testeded here is resume-template PDF generation
// with a font that genuinely covers Hebrew + Arabic + Latin,
// not PDFBox's exact bidi extraction behavior.
}
}
}

private static void renderAndAssertNonEmpty(
DocumentTemplate<CvDocument> template,
CvDocument doc) throws Exception {
Expand Down Expand Up @@ -90,4 +147,88 @@ private static CvDocument fullDocument() {
.build())
.build();
}

private static CvDocument multilingualDocument() {
return CvDocument.builder()
.identity(CvIdentity.builder()
.name("János", "Farkas")
.jobTitle("Senior Platform Engineer")
.contact("+36 30 555 0101", "janos@example.dev", "Budapest")
.link("LinkedIn", "https://linkedin.com/in/janos-farkas")
.link("GitHub", "https://github.com/janos")
.build())
.sections(
new ParagraphSection("Professional Summary",
"Builds reliable multilingual document pipelines. Hungarian sample: "
+ HUNGARIAN_TEXT),
SkillsSection.builder("Technical Skills")
.group("Languages", "Java 21", "Kotlin", "SQL")
.group("Internationalisation", HEBREW_TEXT, ARABIC_TEXT)
.build(),
EntriesSection.builder("Education & Certifications")
.entry("MSc Computer Science",
"Budapest University",
"2019-2021",
"Focused on document systems.")
.build(),
RowsSection.builder("Projects", RowStyle.BULLETED_STACKED)
.row("GraphCompose",
"Resume PDF generation with multilingual content.")
.build(),
EntriesSection.builder("Professional Experience")
.entry("Senior Platform Engineer", "Acme",
"2021-2024",
"Shipped CV rendering and localization workflows.")
.build(),
RowsSection.builder("Additional Information", RowStyle.PLAIN)
.row("Hebrew sample", HEBREW_TEXT)
.row("Arabic sample", ARABIC_TEXT)
.row("Hungarian sample", HUNGARIAN_TEXT)
.build())
.build();
}

private static CvTheme multiscriptTheme() {
CvTheme base = CvTheme.engineeringResume();
CvTypography typography = new CvTypography(
MULTISCRIPT_FONT,
MULTISCRIPT_FONT,
base.typography().sizeHeadline(),
base.typography().sizeContact(),
base.typography().sizeBanner(),
base.typography().sizeEntryTitle(),
base.typography().sizeEntryDate(),
base.typography().sizeEntrySubtitle(),
base.typography().sizeBody(),
base.typography().bodyLineSpacing());
return new CvTheme(base.palette(), typography, base.spacing(), base.decoration());
}

private static Path resolveMultiscriptFont() {
List<Path> candidates = List.of(
Path.of("/Library/Fonts/Arial Unicode.ttf"),
Path.of("/System/Library/Fonts/Supplemental/Arial Unicode.ttf"),
Path.of("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"),
Path.of("/usr/share/fonts/truetype/freefont/FreeSans.ttf"),
// Windows: Tahoma / Arial / Segoe UI all cover Latin + Hebrew + Arabic.
Path.of("C:/Windows/Fonts/tahoma.ttf"),
Path.of("C:/Windows/Fonts/arial.ttf"),
Path.of("C:/Windows/Fonts/segoeui.ttf"));

for (Path candidate : candidates) {
if (Files.isRegularFile(candidate)) {
return candidate;
}
}
return null;
}

private static void maybeWriteMultilingualPdf(byte[] pdfBytes) throws Exception {
if (!SAVE_MULTILINGUAL_PDF) {
return;
}
Path output = Path.of("target", "visual-tests", "cv-v2", "engineering-resume-multilingual.pdf");
Files.createDirectories(output.getParent());
Files.write(output, pdfBytes);
}
}