diff --git a/CHANGELOG.md b/CHANGELOG.md index 44a792bf..7c94c42a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,80 @@ All notable changes to GraphCompose are documented here. Versions follow semantic versioning; release dates are ISO 8601. +## v1.6.4 — Planned + +Bug fix + structured-block patch. Adds two new public Block types — +`WorkHistoryBlock` and `EducationBlock` — that let template authors +declare work-history and education entries with explicit (title, +organisation, date, description) / (degree, institution, year, +details) fields instead of relying on the legacy +`MultiParagraphBlock` pipe-separated string parser. Also closes a +Boxed Sections layout defect that bundled the date and description +into the right-aligned date column for any author-supplied line that +used an em-dash (`" — "`), en-dash (`" – "`), or contained +prose-shaped content the parser misread as a date. **No public API +break** — the sealed `Block` permit list grows from six to eight, +existing `MultiParagraphBlock` work-history strings continue to +parse, and the deprecated parser path stays in place for backward +compatibility. + +### Templates — new structured blocks + +- **`WorkHistoryBlock`.** New public record block carrying a list of + `Item(title, organisation, date, description)` entries. The + `BoxedSections` preset renders each item as a structured row: + title bold on the left, date right-aligned on the same row, + organisation italic on the next line under the title, and + description as a full-width paragraph beneath. Other presets fall + back to a single concatenated paragraph per item. Authors who use + `WorkHistoryBlock` bypass the legacy + `BoxedSections#parseWorkEntry` heuristic parser entirely. +- **`EducationBlock`.** New public record block carrying a list of + `Item(degree, institution, year, details)` entries. Renders with + the same structured layout as `WorkHistoryBlock` (degree bold + left, year right, institution italic, details paragraph) so + Education & Certifications sections visually match Professional + Experience. +- **Sample data migrated.** `ExampleDataFactory.sampleCvSpecV2` now + uses `WorkHistoryBlock` for Professional Experience and + `EducationBlock` for Education & Certifications. The legacy + `MultiParagraphBlock` pattern remains supported and is exercised + by `PresetLayoutSnapshotTest` / `PresetVisualParityTest` to lock + the backward-compat path. + +### Templates — parser robustness (legacy path) + +- **`parseWorkEntry` accepts em-dash and en-dash.** Used to split + the post-pipe segment on ASCII `" - "` only; now tries `" — "`, + `" – "`, and `" - "` in order, mirroring `splitHeading`. Authors + who typed `"*2024-Present* — Led reusable document flows."` saw + the whole tail collapse into the date column — this no longer + happens. +- **`parseWorkEntry` rejects prose dressed up as a date.** The + loose `looksLikeDate` check accepted any string containing a + year and a hyphen anywhere, which caused education lines like + `"... | 2019. First-class honours. Specialisation ..."` to + parse as work entries (the hyphen inside `"First-class"` was + enough to satisfy the heuristic). Parser now rejects post-pipe + segments that contain sentence-ending punctuation (`.`, `:`, + `;`) when no explicit date / description separator was found, + letting these lines fall back to plain paragraph rendering. + Marked `@Deprecated` with a `@deprecated` Javadoc pointing + callers to `WorkHistoryBlock` / `EducationBlock`. +- **`parseProjectItem`** picks up the same em-dash / en-dash / + ASCII separator set so future Project items typed with em-dash + don't regress into "title only" rendering. + +### Tests + +- `BlockTest.blockSealingPermitsAllEightVariants` updated for the + two new permitted block types. +- `PresetVisualGalleryTest.sampleSpec` migrated to + `WorkHistoryBlock` so the visible "primary example" exercises the + new structured shape. +- `PresetLayoutSnapshotTest` intentionally retained on + `MultiParagraphBlock` to lock the legacy parser's behaviour. + ## v1.6.3 — 2026-05-22 Bug fix patch. Closes two independent hyperlink clickable-area diff --git a/examples/src/main/java/com/demcha/examples/support/ExampleDataFactory.java b/examples/src/main/java/com/demcha/examples/support/ExampleDataFactory.java index 39d71b7f..ce5a1035 100644 --- a/examples/src/main/java/com/demcha/examples/support/ExampleDataFactory.java +++ b/examples/src/main/java/com/demcha/examples/support/ExampleDataFactory.java @@ -1,10 +1,12 @@ package com.demcha.examples.support; import com.demcha.compose.document.templates.blocks.BulletListBlock; +import com.demcha.compose.document.templates.blocks.EducationBlock; import com.demcha.compose.document.templates.blocks.IndentedBlock; import com.demcha.compose.document.templates.blocks.KeyValueBlock; import com.demcha.compose.document.templates.blocks.MultiParagraphBlock; import com.demcha.compose.document.templates.blocks.ParagraphBlock; +import com.demcha.compose.document.templates.blocks.WorkHistoryBlock; import com.demcha.compose.document.templates.coverletter.spec.CoverLetterHeader; import com.demcha.compose.document.templates.coverletter.spec.CoverLetterSpec; import com.demcha.compose.document.templates.cv.spec.CvHeader; @@ -286,16 +288,32 @@ public static CvSpec sampleCvSpecV2() { "**Distribution:** Maven Central, Sonatype OSSRH, GPG signing, " + "JitPack, semantic versioning discipline")))) .module(CvModule.of("Education & Certifications", - new MultiParagraphBlock(List.of( - "**MSc Computer Science** - University of Manchester | 2021. " - + "Distinction. Thesis: *Composable layout primitives for " - + "deterministic document rendering*.", - "**BSc Software Engineering** - Imperial College London | 2019. " - + "First-class honours. Specialisation in compilers and " - + "static analysis.", - "**Oracle Java Certification** - Professional track | 2023. " - + "Java 17 platform deep-dive: records, sealed types, " - + "pattern matching, virtual threads.")))) + // Preferred: structured EducationBlock with + // explicit (degree, institution, year, details) + // fields. BoxedSections renders each item with + // the same structured layout as Professional + // Experience — degree bold left, year right, + // institution italic on the next line, and + // details as a full-width paragraph below. + new EducationBlock(List.of( + new EducationBlock.Item( + "MSc Computer Science", + "University of Manchester", + "2021", + "Distinction. Thesis: *Composable layout primitives " + + "for deterministic document rendering*."), + new EducationBlock.Item( + "BSc Software Engineering", + "Imperial College London", + "2019", + "First-class honours. Specialisation in compilers and " + + "static analysis."), + new EducationBlock.Item( + "Oracle Java Certification", + "Professional track", + "2023", + "Java 17 platform deep-dive: records, sealed types, " + + "pattern matching, virtual threads."))))) .module(CvModule.of("Projects", new BulletListBlock(List.of( "**GraphCompose (Java 21, PDFBox, Maven, JMH)** - " @@ -316,23 +334,40 @@ public static CvSpec sampleCvSpecV2() { + "GraphCompose: cinematic covers, pull quotes, " + "multi-column flow, sidebar callouts.")))) .module(CvModule.of("Professional Experience", - new MultiParagraphBlock(List.of( - "**Senior Platform Engineer**, Northwind Systems | " - + "*2024-Present* - Led the reusable document-generation " - + "platform serving billing, hiring, and reporting flows " - + "across **8 product teams**. Reduced template " - + "maintenance time by **70%** by retiring per-team " - + "PDF scripts in favour of one canonical engine.", - "**Software Engineer**, BrightLeaf Labs | *2021-2024* - Built " - + "backend services and production document rendering " - + "pipelines processing **2M+ documents per month**. " - + "Drove the migration from iText to a custom layout " - + "engine, eliminating licensing risk and cutting " - + "p99 render latency from 1.4s to 380ms.", - "**Backend Engineer**, Helix Print Co | *2019-2021* - " - + "Maintained a high-volume invoice-printing service " - + "(15M PDFs/year) and authored the compliance test " - + "harness that gated every template change.")))) + // Preferred: structured WorkHistoryBlock with + // explicit (title, organisation, date, + // description) fields. BoxedSections renders + // each item as a structured row (title bold + // left, date right, organisation italic on the + // next line, description full-width below) + // without falling back to the legacy + // pipe-separated string parser. + new WorkHistoryBlock(List.of( + new WorkHistoryBlock.Item( + "Senior Platform Engineer", + "Northwind Systems", + "2024-Present", + "Led the reusable document-generation platform serving " + + "billing, hiring, and reporting flows across " + + "**8 product teams**. Reduced template maintenance " + + "time by **70%** by retiring per-team PDF scripts " + + "in favour of one canonical engine."), + new WorkHistoryBlock.Item( + "Software Engineer", + "BrightLeaf Labs", + "2021-2024", + "Built backend services and production document rendering " + + "pipelines processing **2M+ documents per month**. " + + "Drove the migration from iText to a custom layout " + + "engine, eliminating licensing risk and cutting " + + "p99 render latency from 1.4s to 380ms."), + new WorkHistoryBlock.Item( + "Backend Engineer", + "Helix Print Co", + "2019-2021", + "Maintained a high-volume invoice-printing service " + + "(15M PDFs/year) and authored the compliance test " + + "harness that gated every template change."))))) .module(CvModule.of("Additional Information", new KeyValueBlock(List.of( new KeyValueBlock.Entry("Languages", diff --git a/src/main/java/com/demcha/compose/document/templates/blocks/Block.java b/src/main/java/com/demcha/compose/document/templates/blocks/Block.java index 2f9110ff..049dba6b 100644 --- a/src/main/java/com/demcha/compose/document/templates/blocks/Block.java +++ b/src/main/java/com/demcha/compose/document/templates/blocks/Block.java @@ -11,8 +11,8 @@ * *

The sealed permit list is intentionally exhaustive: every body * shape that a CV / cover-letter / invoice / proposal preset can - * declare today is one of the six concrete records. To add a new body - * shape, extend the {@code permits} list and update the Module + * declare today is one of the eight concrete records. To add a new + * body shape, extend the {@code permits} list and update the Module * composer to handle the new variant.

* *

Block records are immutable and safe to reuse across documents.

@@ -23,5 +23,7 @@ public sealed interface Block NumberedListBlock, IndentedBlock, KeyValueBlock, - MultiParagraphBlock { + MultiParagraphBlock, + WorkHistoryBlock, + EducationBlock { } diff --git a/src/main/java/com/demcha/compose/document/templates/blocks/EducationBlock.java b/src/main/java/com/demcha/compose/document/templates/blocks/EducationBlock.java new file mode 100644 index 00000000..83c42b36 --- /dev/null +++ b/src/main/java/com/demcha/compose/document/templates/blocks/EducationBlock.java @@ -0,0 +1,84 @@ +package com.demcha.compose.document.templates.blocks; + +import java.util.List; +import java.util.Objects; + +/** + * A {@link Block} that captures a stack of education / certification + * entries with each field (degree, institution, year, details) + * supplied separately so presets can place them precisely without + * re-parsing a concatenated source string. + * + *

This is the preferred shape for "Education", + * "Education & Certifications", "Qualifications" or any module + * whose body is a list of degree / course entries. The + * {@code BoxedSections} preset renders each {@link Item} with the + * same structured layout as {@code WorkHistoryBlock}: degree bold on + * the left, year right-aligned on the same row, institution italic + * on the next line under the degree, and details as a full-width + * paragraph beneath. Other presets fall back to a single inline + * paragraph per item.

+ * + *

Legacy alternative. Authors may still pass + * education as a {@link MultiParagraphBlock} of pipe-separated + * strings — e.g. + * {@code "**Degree** - Institution | Year. Details..."} — and the + * legacy parser tries to interpret them. Prefer + * {@code EducationBlock} in new code: the structured fields are + * explicit, do not depend on the parser's separator and date + * heuristics (which over-trigger on prose containing stray hyphens + * like "First-class"), and survive copy-paste from spreadsheets + * without quoting concerns.

+ * + * @param items education entries in source order, most-recent-first + * by convention (must not be null; may be empty; + * individual items must not be null) + */ +public record EducationBlock(List items) implements Block { + + /** + * Compact constructor that defensively copies the supplied list and + * validates that no item reference is null. + * + * @throws NullPointerException if {@code items} or any element is + * null + */ + public EducationBlock { + Objects.requireNonNull(items, "items"); + items = List.copyOf(items); + } + + /** + * One row in an education stack. All four fields are required + * non-null strings but may be blank — a blank {@code institution} + * collapses the subtitle line, a blank {@code details} collapses + * the body paragraph, and a blank {@code year} renders the degree + * row without a right-aligned year column. + * + * @param degree degree / qualification name, e.g. + * {@code "MSc Computer Science"} or + * {@code "Oracle Java Certification"} + * @param institution awarding institution, e.g. + * {@code "University of Manchester"} or + * {@code "Professional track"} + * @param year year or year range, e.g. {@code "2021"}, + * {@code "2018-2021"} + * @param details additional details (honours, thesis, + * specialisation, course content); free prose, + * may contain inline markdown + * ({@code **bold**}, {@code *italic*}) + */ + public record Item(String degree, String institution, String year, String details) { + + /** + * Compact constructor: rejects null fields. Use empty strings + * for absent values rather than null. + */ + public Item { + Objects.requireNonNull(degree, "degree"); + Objects.requireNonNull(institution, "institution"); + Objects.requireNonNull(year, "year"); + Objects.requireNonNull(details, "details"); + } + } +} diff --git a/src/main/java/com/demcha/compose/document/templates/blocks/WorkHistoryBlock.java b/src/main/java/com/demcha/compose/document/templates/blocks/WorkHistoryBlock.java new file mode 100644 index 00000000..434c6525 --- /dev/null +++ b/src/main/java/com/demcha/compose/document/templates/blocks/WorkHistoryBlock.java @@ -0,0 +1,73 @@ +package com.demcha.compose.document.templates.blocks; + +import java.util.List; +import java.util.Objects; + +/** + * A {@link Block} that captures a stack of work-history entries with + * each field (title, organisation, date, description) supplied + * separately so presets can place them precisely without re-parsing a + * concatenated source string. + * + *

This is the preferred shape for "Professional + * Experience", "Work History", or any module whose body is a list of + * job entries. The {@code BoxedSections} preset renders each + * {@link Item} as a row with the title bold on the left and the date + * right-aligned, the organisation italic on the next line under the + * title, and the description as a full-width paragraph beneath. Other + * presets fall back to a single inline paragraph per item.

+ * + *

Legacy alternative. Authors may still pass work + * history as a {@link MultiParagraphBlock} of pipe-separated strings — + * e.g. {@code "**Title**, Organisation | *Date* — Description"} — and + * {@code BoxedSections} parses that shape for backward compatibility. + * Prefer {@code WorkHistoryBlock} in new code: the structured fields + * are explicit, do not depend on the parser's separator heuristics, + * and survive copy-paste from spreadsheets without quoting concerns.

+ * + * @param items work-history entries in source order, oldest-last by + * convention (must not be null; may be empty; + * individual items must not be null) + */ +public record WorkHistoryBlock(List items) implements Block { + + /** + * Compact constructor that defensively copies the supplied list and + * validates that no item reference is null. + * + * @throws NullPointerException if {@code items} or any element is + * null + */ + public WorkHistoryBlock { + Objects.requireNonNull(items, "items"); + items = List.copyOf(items); + } + + /** + * One row in a work-history stack. All four fields are required + * non-null strings but may be blank — a blank {@code organisation} + * collapses the subtitle line, a blank {@code description} + * collapses the body paragraph, and a blank {@code date} renders + * the title row without a right-aligned date column. + * + * @param title role / position, e.g. {@code "Senior Platform Engineer"} + * @param organisation employer or organisation, e.g. {@code "Northwind Systems"} + * @param date date range or label, e.g. {@code "2024-Present"}, {@code "Jan 2023 – Mar 2024"} + * @param description what the role delivered, free prose; may + * contain inline markdown ({@code **bold**}, + * {@code *italic*}) + */ + public record Item(String title, String organisation, String date, String description) { + + /** + * Compact constructor: rejects null fields. Use empty strings + * for absent values rather than null. + */ + public Item { + Objects.requireNonNull(title, "title"); + Objects.requireNonNull(organisation, "organisation"); + Objects.requireNonNull(date, "date"); + Objects.requireNonNull(description, "description"); + } + } +} diff --git a/src/main/java/com/demcha/compose/document/templates/components/Module.java b/src/main/java/com/demcha/compose/document/templates/components/Module.java index 45eb161c..ad7da1c5 100644 --- a/src/main/java/com/demcha/compose/document/templates/components/Module.java +++ b/src/main/java/com/demcha/compose/document/templates/components/Module.java @@ -14,8 +14,10 @@ import com.demcha.compose.document.templates.blocks.IndentedBlock; import com.demcha.compose.document.templates.blocks.KeyValueBlock; import com.demcha.compose.document.templates.blocks.MultiParagraphBlock; +import com.demcha.compose.document.templates.blocks.EducationBlock; import com.demcha.compose.document.templates.blocks.NumberedListBlock; import com.demcha.compose.document.templates.blocks.ParagraphBlock; +import com.demcha.compose.document.templates.blocks.WorkHistoryBlock; import com.demcha.compose.document.templates.themes.Spacing; import com.demcha.compose.document.theme.BusinessTheme; @@ -176,9 +178,152 @@ private DocumentNode renderBody(BusinessTheme theme, Spacing spacing) { if (body instanceof KeyValueBlock k) { return renderKeyValue(k, theme, spacing); } + if (body instanceof WorkHistoryBlock w) { + return renderWorkHistory(w, theme, spacing); + } + if (body instanceof EducationBlock e) { + return renderEducation(e, theme, spacing); + } throw new IllegalStateException("Unsupported module body: " + body); } + /** + * Default rendering for {@link WorkHistoryBlock} when the active + * preset has not overridden the dispatch (e.g. {@code BoxedSections} + * renders this block as a structured title/date row + company + * subtitle + description paragraph). Generic presets degrade to a + * single concatenated paragraph per item, mirroring the legacy + * {@code MultiParagraphBlock} format + * ({@code "**Title**, Organisation | *Date* — Description"}) so + * the data still renders sensibly without requiring every preset + * to special-case the new block type. + */ + private DocumentNode renderWorkHistory(WorkHistoryBlock block, BusinessTheme theme, Spacing spacing) { + List sources = new ArrayList<>(block.items().size()); + for (WorkHistoryBlock.Item item : block.items()) { + sources.add(concatStructuredFields( + item.title(), item.organisation(), item.date(), item.description(), + ", ", " | ", " — ", + /* italicizeDate */ true)); + } + return renderConcatenatedParagraphs(sources, "workEntry", theme, spacing); + } + + /** + * Default rendering for {@link EducationBlock} when the active + * preset has not overridden the dispatch (e.g. {@code BoxedSections} + * renders this block with the same structured layout as a work + * entry — degree bold left, year right, institution italic below, + * details paragraph beneath). Generic presets degrade to a single + * concatenated paragraph per item so the data still renders + * sensibly without requiring every preset to special-case the new + * block type. + */ + private DocumentNode renderEducation(EducationBlock block, BusinessTheme theme, Spacing spacing) { + List sources = new ArrayList<>(block.items().size()); + for (EducationBlock.Item item : block.items()) { + sources.add(concatStructuredFields( + item.degree(), item.institution(), item.year(), item.details(), + " - ", " | ", ". ", + /* italicizeDate */ false)); + } + return renderConcatenatedParagraphs(sources, "educationEntry", theme, spacing); + } + + /** + * Concatenates the four fields of a structured entry (work history + * or education) into a single markdown source line, using the + * supplied separators. The first field is rendered bold, the third + * is optionally italicized, and the others stay in regular weight. + * Empty fields are skipped along with their preceding separator so + * a missing organisation / date / description does not leave a + * dangling {@code " | "} or {@code " — "} on screen. + * + *

Extracted from {@link #renderWorkHistory} and + * {@link #renderEducation} so adding a future 4-field structured + * block (publications, projects, etc.) does not require another + * copy of the same string-building loop.

+ */ + private static String concatStructuredFields( + String main, String secondary, String date, String detail, + String secondarySep, String dateSep, String detailSep, + boolean italicizeDate) { + StringBuilder source = new StringBuilder(); + if (main != null && !main.isBlank()) { + source.append("**").append(main).append("**"); + } + if (secondary != null && !secondary.isBlank()) { + if (source.length() > 0) { + source.append(secondarySep); + } + source.append(secondary); + } + if (date != null && !date.isBlank()) { + if (source.length() > 0) { + source.append(dateSep); + } + if (italicizeDate) { + source.append('*').append(date).append('*'); + } else { + source.append(date); + } + } + if (detail != null && !detail.isBlank()) { + if (source.length() > 0) { + source.append(detailSep); + } + source.append(detail); + } + return source.toString(); + } + + /** + * Wraps a list of pre-built markdown source strings into a + * {@link ContainerNode} of {@link ParagraphNode}s using the active + * body style and paragraph spacing. Extracted from + * {@link #renderWorkHistory} and {@link #renderEducation} so the + * boilerplate around constructing ParagraphNode + ContainerNode + * lives in one place — any future 4-field structured block can + * call this directly after building its own per-item source via + * {@link #concatStructuredFields}. + * + * @param sources per-item markdown source lines + * @param entryNameSuffix name suffix appended to {@code name} + * for each paragraph (e.g. + * {@code "workEntry"} → {@code ".workEntry[0]"}) + */ + private DocumentNode renderConcatenatedParagraphs(List sources, String entryNameSuffix, + BusinessTheme theme, Spacing spacing) { + DocumentTextStyle bodyStyle = bodyStyle(theme); + List paragraphs = new ArrayList<>(sources.size()); + for (int i = 0; i < sources.size(); i++) { + paragraphs.add(new ParagraphNode( + name + "." + entryNameSuffix + "[" + i + "]", + "", + MarkdownText.parse(sources.get(i), bodyStyle), + bodyStyle, + TextAlign.LEFT, + spacing.lineSpacing(), + "", + null, + null, + null, + DocumentInsets.zero(), + DocumentInsets.zero(), + null)); + } + return new ContainerNode( + name + ".body", + paragraphs, + /* spacing */ spacing.paragraphSpacing(), + /* padding */ leftIndent(spacing), + DocumentInsets.zero(), + null, + null, + null, + null); + } + private ParagraphNode renderParagraph(ParagraphBlock block, BusinessTheme theme, Spacing spacing) { DocumentTextStyle bodyStyle = bodyStyle(theme); List runs = MarkdownText.parse(block.text(), bodyStyle); diff --git a/src/main/java/com/demcha/compose/document/templates/cv/presets/BoxedSections.java b/src/main/java/com/demcha/compose/document/templates/cv/presets/BoxedSections.java index eb0282e0..a54c87ac 100644 --- a/src/main/java/com/demcha/compose/document/templates/cv/presets/BoxedSections.java +++ b/src/main/java/com/demcha/compose/document/templates/cv/presets/BoxedSections.java @@ -20,8 +20,10 @@ import com.demcha.compose.document.templates.blocks.IndentedBlock; import com.demcha.compose.document.templates.blocks.KeyValueBlock; import com.demcha.compose.document.templates.blocks.MultiParagraphBlock; +import com.demcha.compose.document.templates.blocks.EducationBlock; import com.demcha.compose.document.templates.blocks.NumberedListBlock; import com.demcha.compose.document.templates.blocks.ParagraphBlock; +import com.demcha.compose.document.templates.blocks.WorkHistoryBlock; import com.demcha.compose.document.templates.cv.spec.CvHeader; import com.demcha.compose.document.templates.cv.spec.CvModule; import com.demcha.compose.document.templates.cv.spec.CvSpec; @@ -217,6 +219,30 @@ private void addModuleBody(SectionBuilder section, CvModule module) { private void renderBody(SectionBuilder section, Block body) { if (body instanceof ParagraphBlock p) { renderParagraph(section, p.text()); + } else if (body instanceof WorkHistoryBlock w) { + // Preferred structured shape — render each item directly + // via renderWorkEntry, bypassing the separator-heuristic + // parser that the MultiParagraphBlock legacy path uses. + for (WorkHistoryBlock.Item item : w.items()) { + renderWorkEntry(section, new WorkEntry( + safe(item.title()).trim(), + safe(item.organisation()).trim(), + safe(item.date()).trim(), + safe(item.description()).trim())); + } + } else if (body instanceof EducationBlock e) { + // Preferred structured shape for education / certs — + // maps onto the same WorkEntry render so the visual + // layout (degree bold left, year right, institution + // italic below, details paragraph beneath) stays + // consistent with Professional Experience. + for (EducationBlock.Item item : e.items()) { + renderWorkEntry(section, new WorkEntry( + safe(item.degree()).trim(), + safe(item.institution()).trim(), + safe(item.year()).trim(), + safe(item.details()).trim())); + } } else if (body instanceof MultiParagraphBlock m) { for (String line : m.paragraphs()) { WorkEntry entry = parseWorkEntry(line); @@ -248,9 +274,49 @@ private void renderBody(SectionBuilder section, Block body) { } } else if (body instanceof KeyValueBlock kv) { for (KeyValueBlock.Entry entry : kv.entries()) { - renderParagraph(section, entry.key() + ": " + entry.value()); + renderKeyValueEntry(section, entry); + } + } + } + + /** + * Renders a {@link KeyValueBlock} entry as "Key: value" + * — the label rendered bold, the colon attached, then the + * value in regular weight. Improves readability over the + * legacy {@code key + ": " + value} flat-text path that + * rendered the entire line in body weight and left "Languages", + * "Open Source", and other labels visually indistinguishable + * from the prose that followed. + */ + private void renderKeyValueEntry(SectionBuilder section, KeyValueBlock.Entry entry) { + String key = safe(entry.key()).trim(); + String value = safe(entry.value()).trim(); + if (key.isBlank() && value.isBlank()) { + return; + } + DocumentTextStyle base = style(BODY_FONT, 8.6, + DocumentTextDecoration.DEFAULT, INK); + // Wrap the key in **markdown bold** markers so the existing + // appendMarkdown parser emits a bold inline run for it and + // the value continues with the base body style. Authors who + // already typed markdown inside key or value keep their + // emphasis runs. + StringBuilder source = new StringBuilder(); + if (!key.isBlank()) { + source.append("**").append(key).append(":**"); + } + if (!value.isBlank()) { + if (source.length() > 0) { + source.append(' '); } + source.append(value); } + section.addParagraph(paragraph -> paragraph + .textStyle(base) + .lineSpacing(1.4) + .align(TextAlign.LEFT) + .margin(DocumentInsets.top(2)) + .rich(rich -> appendMarkdown(rich, source.toString(), base))); } private void renderParagraph(SectionBuilder section, String rawLine) { @@ -455,6 +521,26 @@ private static void addPart(List parts, String text, } } + /** + * Parses a single pipe-separated work-history line in the legacy + * format + * {@code "**Title**, Organisation | *Date* — Description"}. Accepts + * em-dash, en-dash, and ASCII hyphen as the date / description + * separator, mirroring {@link #splitHeading}. Returns {@code null} + * when the input does not look like a work-history line (no pipe, + * no recognisable date) so callers can fall back to plain paragraph + * rendering. + * + * @deprecated Backward-compatibility path for callers who feed + * work history via {@link MultiParagraphBlock}. + * New code should declare work entries via + * {@link WorkHistoryBlock} with explicit + * {@code (title, organisation, date, description)} + * fields — that route bypasses this heuristic parser + * entirely and renders directly via + * {@link #renderWorkEntry}. + */ + @Deprecated private static WorkEntry parseWorkEntry(String item) { String clean = stripBasicMarkdown(safe(item).trim()); int pipeIndex = clean.indexOf('|'); @@ -468,20 +554,68 @@ private static WorkEntry parseWorkEntry(String item) { } String date; String description = ""; - int dashIdx = afterPipe.indexOf(" - "); - if (dashIdx > 0) { + // Date/description separator: accept em-dash " — ", en-dash " – ", + // and ASCII " - " (matching splitHeading). Without this, authors + // who type " — " in the data file get the whole date+description + // collapsed into the date column and an empty description + // paragraph beneath — the right-aligned date column then wraps + // the description text and the work entry's description never + // renders on its own full-width line under the company subtitle. + String separator = null; + int dashIdx = -1; + for (String candidate : new String[]{" — ", " – ", " - "}) { + int idx = afterPipe.indexOf(candidate); + if (idx > 0) { + dashIdx = idx; + separator = candidate; + break; + } + } + if (separator != null) { date = afterPipe.substring(0, dashIdx).trim(); - description = afterPipe.substring(dashIdx + 3).trim(); + description = afterPipe.substring(dashIdx + separator.length()).trim(); } else { date = afterPipe; } if (!looksLikeDate(date)) { return null; } + // Reject lines whose post-pipe segment is prose dressed up as a + // date. Education entries like + // {@code "**BSc** - Uni | 2019. First-class honours. ..."} used + // to satisfy looksLikeDate via the hyphen inside "First-class" + // and would collapse the whole sentence into the right-aligned + // date column, hiding the description text. A genuine date is + // either a year, year range, or short month-year token without + // sentence-ending punctuation; if no explicit date/description + // separator was found AND the candidate date contains a period + // (or any character that signals prose) the line is treated as + // a non-work entry and the caller falls back to plain paragraph + // rendering. + if (separator == null && containsProseSignals(date)) { + return null; + } HeadingParts heading = splitHeading(headingText); return new WorkEntry(heading.main(), heading.sub(), date, description); } + /** + * Detects characters that signal the candidate string is prose + * rather than a date or date range. Used by {@link #parseWorkEntry} + * as a defensive second pass when the loose {@link #looksLikeDate} + * heuristic accepts a sentence containing a stray hyphen / em-dash + * (e.g. {@code "2019. First-class honours."} would otherwise count + * as a date because of the hyphen inside {@code "First-class"}). + */ + private static boolean containsProseSignals(String value) { + if (value == null || value.isBlank()) { + return false; + } + return value.contains(".") + || value.contains(":") + || value.contains(";"); + } + private static HeadingParts splitHeading(String heading) { for (String separator : new String[]{" – ", " — ", " - "}) { int idx = heading.indexOf(separator); @@ -531,15 +665,25 @@ private static boolean isProjectsModule(String title) { } private static ProjectParts parseProjectItem(String item) { - // Split on " - " (space-hyphen-space, mirroring WorkEntry parsing) - // so an em-dash or hyphen inside the description is not eaten. - // Falls back to "title only" when no separator is present. - int sepIndex = item.indexOf(" - "); - if (sepIndex <= 0) { + // Split on " — " (em-dash), " – " (en-dash), or " - " (ASCII), + // mirroring WorkEntry parsing so an em-dash inside the source + // line doesn't eat the description into the name. Falls back + // to "title only" when no separator is present. + String separator = null; + int sepIndex = -1; + for (String candidate : new String[]{" — ", " – ", " - "}) { + int idx = item.indexOf(candidate); + if (idx > 0) { + sepIndex = idx; + separator = candidate; + break; + } + } + if (separator == null) { return new ProjectParts(item.trim(), ""); } String name = item.substring(0, sepIndex).trim(); - String description = item.substring(sepIndex + 3).trim(); + String description = item.substring(sepIndex + separator.length()).trim(); return new ProjectParts(name, description); } diff --git a/src/test/java/com/demcha/compose/document/templates/blocks/BlockTest.java b/src/test/java/com/demcha/compose/document/templates/blocks/BlockTest.java index 8058efd7..bc30971c 100644 --- a/src/test/java/com/demcha/compose/document/templates/blocks/BlockTest.java +++ b/src/test/java/com/demcha/compose/document/templates/blocks/BlockTest.java @@ -221,11 +221,11 @@ void keyValueBlockIsImmutable() { // Sealing --------------------------------------------------------- @Test - void blockSealingPermitsAllSixVariants() { - // The sealed permit list of Block must include all six concrete - // block kinds. This is essentially a compile-time guarantee, but - // the test fails fast if the permit list ever drifts from the - // record set in this package. + void blockSealingPermitsAllEightVariants() { + // The sealed permit list of Block must include all eight + // concrete block kinds. This is essentially a compile-time + // guarantee, but the test fails fast if the permit list ever + // drifts from the record set in this package. Class[] permitted = Block.class.getPermittedSubclasses(); assertThat(permitted) .containsExactlyInAnyOrder( @@ -234,6 +234,8 @@ void blockSealingPermitsAllSixVariants() { NumberedListBlock.class, IndentedBlock.class, KeyValueBlock.class, - MultiParagraphBlock.class); + MultiParagraphBlock.class, + WorkHistoryBlock.class, + EducationBlock.class); } } diff --git a/src/test/java/com/demcha/compose/document/templates/cv/presets/BoxedSectionsStructuredRegressionTest.java b/src/test/java/com/demcha/compose/document/templates/cv/presets/BoxedSectionsStructuredRegressionTest.java new file mode 100644 index 00000000..867e2b12 --- /dev/null +++ b/src/test/java/com/demcha/compose/document/templates/cv/presets/BoxedSectionsStructuredRegressionTest.java @@ -0,0 +1,160 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.TemplateTestSupport; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.blocks.EducationBlock; +import com.demcha.compose.document.templates.blocks.KeyValueBlock; +import com.demcha.compose.document.templates.blocks.MultiParagraphBlock; +import com.demcha.compose.document.templates.blocks.WorkHistoryBlock; +import com.demcha.compose.document.templates.cv.spec.CvHeader; +import com.demcha.compose.document.templates.cv.spec.CvModule; +import com.demcha.compose.document.templates.cv.spec.CvSpec; +import com.demcha.compose.document.theme.BusinessTheme; +import org.apache.pdfbox.pdmodel.common.PDRectangle; +import org.junit.jupiter.api.Test; + +import java.util.List; + +/** + * Regression tests for the v1.6.4 BoxedSections changes: + * + *
    + *
  • {@link WorkHistoryBlock} renders each item with the structured + * title / date / organisation / description layout (not the + * legacy "everything on one line" fallback).
  • + *
  • {@link EducationBlock} renders each item with the same + * structured layout (degree / year / institution / details).
  • + *
  • {@link KeyValueBlock} renders each entry as bold key: + * followed by a regular-weight value.
  • + *
  • {@link MultiParagraphBlock} entries whose post-pipe segment is + * prose containing sentence-ending punctuation (e.g. + * {@code "... | 2019. First-class honours. Specialisation ..."}) + * are rejected by the deprecated {@code parseWorkEntry} parser + * and fall back to plain paragraph rendering instead of + * collapsing the description into the date column.
  • + *
+ * + *

Each test renders a focused {@link CvSpec} into an in-memory + * session and compares the resulting layout tree against a checked-in + * baseline JSON under + * {@code src/test/resources/layout-snapshots/canonical-templates/cv-v2/regression/}. + * Re-run with {@code -Dgraphcompose.updateSnapshots=true} to refresh + * a baseline after an intentional change.

+ */ +class BoxedSectionsStructuredRegressionTest { + + private static final BusinessTheme THEME = BusinessTheme.modern(); + + private static CvHeader header() { + return CvHeader.builder() + .name("Artem Demchyshyn") + .address("London, UK") + .email("artem@demo.dev") + .build(); + } + + @Test + void workHistoryBlockRendersStructuredLayout() throws Exception { + CvSpec spec = CvSpec.builder() + .header(header()) + .module(CvModule.of("Professional Experience", + new WorkHistoryBlock(List.of( + new WorkHistoryBlock.Item( + "Senior Platform Engineer", + "Northwind Systems", + "2024-Present", + "Led the reusable document-generation platform."), + new WorkHistoryBlock.Item( + "Software Engineer", + "BrightLeaf Labs", + "2021-2024", + "Built backend services and rendering pipelines."))))) + .build(); + DocumentTemplate template = BoxedSections.create(THEME); + try (DocumentSession document = TemplateTestSupport.openInMemoryDocument( + PDRectangle.A4, 15, 10, 15, 15)) { + template.compose(document, spec); + TemplateTestSupport.assertCanonicalSnapshot(document, + "boxed_sections_work_history_block", "cv-v2", "regression"); + } + } + + @Test + void educationBlockRendersStructuredLayout() throws Exception { + CvSpec spec = CvSpec.builder() + .header(header()) + .module(CvModule.of("Education & Certifications", + new EducationBlock(List.of( + new EducationBlock.Item( + "MSc Computer Science", + "University of Manchester", + "2021", + "Distinction. Thesis: layout primitives for deterministic rendering."), + new EducationBlock.Item( + "Oracle Java Certification", + "Professional track", + "2023", + "Java 17 platform deep-dive: records, sealed types, pattern matching."))))) + .build(); + DocumentTemplate template = BoxedSections.create(THEME); + try (DocumentSession document = TemplateTestSupport.openInMemoryDocument( + PDRectangle.A4, 15, 10, 15, 15)) { + template.compose(document, spec); + TemplateTestSupport.assertCanonicalSnapshot(document, + "boxed_sections_education_block", "cv-v2", "regression"); + } + } + + @Test + void keyValueBlockRendersKeysInBold() throws Exception { + CvSpec spec = CvSpec.builder() + .header(header()) + .module(CvModule.of("Additional Information", + new KeyValueBlock(List.of( + new KeyValueBlock.Entry("Languages", + "English (Fluent), German (Intermediate)"), + new KeyValueBlock.Entry("Work Eligibility", + "Eligible to work in the UK and the EU"), + new KeyValueBlock.Entry("Open Source", + "Maintainer of GraphCompose."))))) + .build(); + DocumentTemplate template = BoxedSections.create(THEME); + try (DocumentSession document = TemplateTestSupport.openInMemoryDocument( + PDRectangle.A4, 15, 10, 15, 15)) { + template.compose(document, spec); + TemplateTestSupport.assertCanonicalSnapshot(document, + "boxed_sections_key_value_bold_key", "cv-v2", "regression"); + } + } + + @Test + void multiParagraphProseDoesNotCollapseIntoDateColumn() throws Exception { + // Locks the parseWorkEntry prose-rejection: lines like + // "... | 2019. First-class honours. Specialisation ..." used + // to collapse the whole post-pipe segment into the right- + // aligned date column (the hyphen inside "First-class" tricked + // looksLikeDate into accepting the prose as a date). The + // deprecated parser now bails on sentence-ending punctuation + // when no explicit date/description separator is present and + // BoxedSections renders the line as a plain paragraph instead. + CvSpec spec = CvSpec.builder() + .header(header()) + .module(CvModule.of("Education & Certifications", + new MultiParagraphBlock(List.of( + "**BSc Software Engineering** - Imperial College London | " + + "2019. First-class honours. Specialisation in " + + "compilers and static analysis.", + "**Oracle Java Certification** - Professional track | " + + "2023. Java 17 platform deep-dive: records, " + + "sealed types, pattern matching.")))) + .build(); + DocumentTemplate template = BoxedSections.create(THEME); + try (DocumentSession document = TemplateTestSupport.openInMemoryDocument( + PDRectangle.A4, 15, 10, 15, 15)) { + template.compose(document, spec); + TemplateTestSupport.assertCanonicalSnapshot(document, + "boxed_sections_prose_rejection_legacy_path", "cv-v2", "regression"); + } + } +} diff --git a/src/test/java/com/demcha/compose/document/templates/cv/presets/PresetLayoutSnapshotTest.java b/src/test/java/com/demcha/compose/document/templates/cv/presets/PresetLayoutSnapshotTest.java index 52ce1574..8ee21172 100644 --- a/src/test/java/com/demcha/compose/document/templates/cv/presets/PresetLayoutSnapshotTest.java +++ b/src/test/java/com/demcha/compose/document/templates/cv/presets/PresetLayoutSnapshotTest.java @@ -73,6 +73,14 @@ private static CvSpec canonicalCvSpec() { + "*2024-Present* — Led reusable document flows.", "**Software Engineer**, BrightLeaf Labs | *2021-2024* " + "— Built backend services and rendering pipelines.")))) + /* Note: this snapshot test intentionally exercises the + * legacy MultiParagraphBlock pipe-separated string path + * to pin the parser's em-dash / en-dash / ASCII-hyphen + * separator handling. The structurally equivalent + * preferred path uses WorkHistoryBlock — see + * PresetVisualGalleryTest#sampleSpec for the canonical + * example. Both paths converge on renderWorkEntry and + * produce the same LayoutGraph for equivalent data. */ .module(CvModule.of("Additional Information", new KeyValueBlock(List.of( new KeyValueBlock.Entry("Location", diff --git a/src/test/java/com/demcha/compose/document/templates/cv/presets/PresetVisualGalleryTest.java b/src/test/java/com/demcha/compose/document/templates/cv/presets/PresetVisualGalleryTest.java index 0b2cd780..ba7670df 100644 --- a/src/test/java/com/demcha/compose/document/templates/cv/presets/PresetVisualGalleryTest.java +++ b/src/test/java/com/demcha/compose/document/templates/cv/presets/PresetVisualGalleryTest.java @@ -8,7 +8,7 @@ import com.demcha.compose.document.templates.blocks.BulletListBlock; import com.demcha.compose.document.templates.blocks.IndentedBlock; import com.demcha.compose.document.templates.blocks.KeyValueBlock; -import com.demcha.compose.document.templates.blocks.MultiParagraphBlock; +import com.demcha.compose.document.templates.blocks.WorkHistoryBlock; import com.demcha.compose.document.templates.blocks.ParagraphBlock; import com.demcha.compose.document.templates.cv.spec.CvHeader; import com.demcha.compose.document.templates.cv.spec.CvModule; @@ -76,11 +76,22 @@ private static CvSpec sampleSpec() { "Template Studio (Kotlin, Compose Desktop)", "Internal tool for evaluating CV, proposal, and invoice output"))))) .module(CvModule.of("Professional Experience", - new MultiParagraphBlock(List.of( - "**Senior Platform Engineer**, Northwind Systems | " - + "*2024-Present* — Led reusable document flows.", - "**Software Engineer**, BrightLeaf Labs | *2021-2024* " - + "— Built backend services and rendering pipelines.")))) + // Preferred: structured WorkHistoryBlock with + // separate title / organisation / date / + // description fields. BoxedSections routes this + // straight to renderWorkEntry, bypassing the + // legacy pipe-separated string parser. + new WorkHistoryBlock(List.of( + new WorkHistoryBlock.Item( + "Senior Platform Engineer", + "Northwind Systems", + "2024-Present", + "Led reusable document flows."), + new WorkHistoryBlock.Item( + "Software Engineer", + "BrightLeaf Labs", + "2021-2024", + "Built backend services and rendering pipelines."))))) .module(CvModule.of("Additional Information", new KeyValueBlock(List.of( new KeyValueBlock.Entry("Location", diff --git a/src/test/resources/layout-snapshots/canonical-templates/cv-v2/regression/boxed_sections_education_block.json b/src/test/resources/layout-snapshots/canonical-templates/cv-v2/regression/boxed_sections_education_block.json new file mode 100644 index 00000000..249b3ea4 --- /dev/null +++ b/src/test/resources/layout-snapshots/canonical-templates/cv-v2/regression/boxed_sections_education_block.json @@ -0,0 +1,677 @@ +{ + "formatVersion" : "2.0", + "canvas" : { + "pageWidth" : 595.276, + "pageHeight" : 841.89, + "innerWidth" : 570.276, + "innerHeight" : 811.89, + "margin" : { + "top" : 15.0, + "right" : 10.0, + "bottom" : 15.0, + "left" : 15.0 + } + }, + "totalPages" : 1, + "nodes" : [ { + "path" : "BoxedSectionsRoot[0]", + "entityName" : "BoxedSectionsRoot", + "entityKind" : "ContainerNode", + "parentPath" : null, + "childIndex" : 0, + "depth" : 1, + "layer" : 1, + "computedX" : 15.0, + "computedY" : 629.99, + "placementX" : 15.0, + "placementY" : 629.99, + "placementWidth" : 570.276, + "placementHeight" : 196.9, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 570.276, + "contentHeight" : 196.9, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsHeader[0]", + "entityName" : "BoxedSectionsHeader", + "entityKind" : "SectionNode", + "parentPath" : "BoxedSectionsRoot[0]", + "childIndex" : 0, + "depth" : 2, + "layer" : 2, + "computedX" : 15.0, + "computedY" : 794.402, + "placementX" : 15.0, + "placementY" : 794.402, + "placementWidth" : 570.276, + "placementHeight" : 32.488, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 570.276, + "contentHeight" : 32.488, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 4.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsHeader[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsHeader[0]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 15.0, + "computedY" : 798.402, + "placementX" : 15.0, + "placementY" : 798.402, + "placementWidth" : 570.276, + "placementHeight" : 28.488, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 570.276, + "contentHeight" : 28.488, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsContact[1]", + "entityName" : "BoxedSectionsContact", + "entityKind" : "SectionNode", + "parentPath" : "BoxedSectionsRoot[0]", + "childIndex" : 1, + "depth" : 2, + "layer" : 2, + "computedX" : 15.0, + "computedY" : 768.14, + "placementX" : 15.0, + "placementY" : 768.14, + "placementWidth" : 570.276, + "placementHeight" : 19.263, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 570.276, + "contentHeight" : 19.263, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 4.0, + "right" : 0.0, + "bottom" : 4.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsContact[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsContact[1]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 15.0, + "computedY" : 772.14, + "placementX" : 15.0, + "placementY" : 772.14, + "placementWidth" : 570.276, + "placementHeight" : 11.263, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 570.276, + "contentHeight" : 11.263, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBanner_0[2]", + "entityName" : "BoxedSectionsBanner_0", + "entityKind" : "SectionNode", + "parentPath" : "BoxedSectionsRoot[0]", + "childIndex" : 2, + "depth" : 2, + "layer" : 2, + "computedX" : 15.0, + "computedY" : 734.42, + "placementX" : 15.0, + "placementY" : 734.42, + "placementWidth" : 570.276, + "placementHeight" : 22.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 570.276, + "contentHeight" : 22.72, + "margin" : { + "top" : 4.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 5.0, + "right" : 5.0, + "bottom" : 5.0, + "left" : 5.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBanner_0[2]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsBanner_0[2]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 20.0, + "computedY" : 739.42, + "placementX" : 20.0, + "placementY" : 739.42, + "placementWidth" : 560.276, + "placementHeight" : 12.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 560.276, + "contentHeight" : 12.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]", + "entityName" : "BoxedSectionsBody_0", + "entityKind" : "SectionNode", + "parentPath" : "BoxedSectionsRoot[0]", + "childIndex" : 3, + "depth" : 2, + "layer" : 2, + "computedX" : 15.0, + "computedY" : 629.99, + "placementX" : 15.0, + "placementY" : 629.99, + "placementWidth" : 570.276, + "placementHeight" : 97.43, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 570.276, + "contentHeight" : 97.43, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 4.0, + "right" : 4.0, + "bottom" : 0.0, + "left" : 4.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]/BoxedSectionsEntryHeader[0]", + "entityName" : "BoxedSectionsEntryHeader", + "entityKind" : "RowNode", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 19.0, + "computedY" : 711.23, + "placementX" : 19.0, + "placementY" : 711.23, + "placementWidth" : 562.276, + "placementHeight" : 12.19, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 562.276, + "contentHeight" : 12.19, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]/BoxedSectionsEntryHeader[0]/Title[0]", + "entityName" : "Title", + "entityKind" : "SectionNode", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]/BoxedSectionsEntryHeader[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 19.0, + "computedY" : 711.23, + "placementX" : 19.0, + "placementY" : 711.23, + "placementWidth" : 99.875, + "placementHeight" : 12.19, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 99.875, + "contentHeight" : 12.19, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]/BoxedSectionsEntryHeader[0]/Title[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]/BoxedSectionsEntryHeader[0]/Title[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 19.0, + "computedY" : 711.23, + "placementX" : 19.0, + "placementY" : 711.23, + "placementWidth" : 99.875, + "placementHeight" : 12.19, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 99.875, + "contentHeight" : 12.19, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]/BoxedSectionsEntryHeader[0]/Date[1]", + "entityName" : "Date", + "entityKind" : "SectionNode", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]/BoxedSectionsEntryHeader[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 409.259, + "computedY" : 711.76, + "placementX" : 409.259, + "placementY" : 711.76, + "placementWidth" : 172.017, + "placementHeight" : 11.66, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 172.017, + "contentHeight" : 11.66, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]/BoxedSectionsEntryHeader[0]/Date[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]/BoxedSectionsEntryHeader[0]/Date[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 409.259, + "computedY" : 711.76, + "placementX" : 409.259, + "placementY" : 711.76, + "placementWidth" : 172.017, + "placementHeight" : 11.66, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 172.017, + "contentHeight" : 11.66, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 19.0, + "computedY" : 696.1, + "placementX" : 19.0, + "placementY" : 696.1, + "placementWidth" : 88.393, + "placementHeight" : 11.13, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 88.393, + "contentHeight" : 11.13, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]", + "childIndex" : 2, + "depth" : 3, + "layer" : 3, + "computedX" : 19.0, + "computedY" : 678.705, + "placementX" : 19.0, + "placementY" : 678.705, + "placementWidth" : 250.432, + "placementHeight" : 11.395, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 250.432, + "contentHeight" : 11.395, + "margin" : { + "top" : 2.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]/BoxedSectionsEntryHeader[3]", + "entityName" : "BoxedSectionsEntryHeader", + "entityKind" : "RowNode", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]", + "childIndex" : 3, + "depth" : 3, + "layer" : 3, + "computedX" : 19.0, + "computedY" : 662.515, + "placementX" : 19.0, + "placementY" : 662.515, + "placementWidth" : 562.276, + "placementHeight" : 12.19, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 562.276, + "contentHeight" : 12.19, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]/BoxedSectionsEntryHeader[3]/Title[0]", + "entityName" : "Title", + "entityKind" : "SectionNode", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]/BoxedSectionsEntryHeader[3]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 19.0, + "computedY" : 662.515, + "placementX" : 19.0, + "placementY" : 662.515, + "placementWidth" : 107.806, + "placementHeight" : 12.19, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 107.806, + "contentHeight" : 12.19, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]/BoxedSectionsEntryHeader[3]/Title[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]/BoxedSectionsEntryHeader[3]/Title[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 19.0, + "computedY" : 662.515, + "placementX" : 19.0, + "placementY" : 662.515, + "placementWidth" : 107.806, + "placementHeight" : 12.19, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 107.806, + "contentHeight" : 12.19, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]/BoxedSectionsEntryHeader[3]/Date[1]", + "entityName" : "Date", + "entityKind" : "SectionNode", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]/BoxedSectionsEntryHeader[3]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 409.259, + "computedY" : 663.045, + "placementX" : 409.259, + "placementY" : 663.045, + "placementWidth" : 172.017, + "placementHeight" : 11.66, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 172.017, + "contentHeight" : 11.66, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]/BoxedSectionsEntryHeader[3]/Date[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]/BoxedSectionsEntryHeader[3]/Date[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 409.259, + "computedY" : 663.045, + "placementX" : 409.259, + "placementY" : 663.045, + "placementWidth" : 172.017, + "placementHeight" : 11.66, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 172.017, + "contentHeight" : 11.66, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]/ParagraphNode[4]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]", + "childIndex" : 4, + "depth" : 3, + "layer" : 3, + "computedX" : 19.0, + "computedY" : 647.385, + "placementX" : 19.0, + "placementY" : 647.385, + "placementWidth" : 64.05, + "placementHeight" : 11.13, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 64.05, + "contentHeight" : 11.13, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]/ParagraphNode[5]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]", + "childIndex" : 5, + "depth" : 3, + "layer" : 3, + "computedX" : 19.0, + "computedY" : 629.99, + "placementX" : 19.0, + "placementY" : 629.99, + "placementWidth" : 260.632, + "placementHeight" : 11.395, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 260.632, + "contentHeight" : 11.395, + "margin" : { + "top" : 2.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + } ] +} diff --git a/src/test/resources/layout-snapshots/canonical-templates/cv-v2/regression/boxed_sections_key_value_bold_key.json b/src/test/resources/layout-snapshots/canonical-templates/cv-v2/regression/boxed_sections_key_value_bold_key.json new file mode 100644 index 00000000..04c294fe --- /dev/null +++ b/src/test/resources/layout-snapshots/canonical-templates/cv-v2/regression/boxed_sections_key_value_bold_key.json @@ -0,0 +1,347 @@ +{ + "formatVersion" : "2.0", + "canvas" : { + "pageWidth" : 595.276, + "pageHeight" : 841.89, + "innerWidth" : 570.276, + "innerHeight" : 811.89, + "margin" : { + "top" : 15.0, + "right" : 10.0, + "bottom" : 15.0, + "left" : 15.0 + } + }, + "totalPages" : 1, + "nodes" : [ { + "path" : "BoxedSectionsRoot[0]", + "entityName" : "BoxedSectionsRoot", + "entityKind" : "ContainerNode", + "parentPath" : null, + "childIndex" : 0, + "depth" : 1, + "layer" : 1, + "computedX" : 15.0, + "computedY" : 675.235, + "placementX" : 15.0, + "placementY" : 675.235, + "placementWidth" : 570.276, + "placementHeight" : 151.655, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 570.276, + "contentHeight" : 151.655, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsHeader[0]", + "entityName" : "BoxedSectionsHeader", + "entityKind" : "SectionNode", + "parentPath" : "BoxedSectionsRoot[0]", + "childIndex" : 0, + "depth" : 2, + "layer" : 2, + "computedX" : 15.0, + "computedY" : 794.402, + "placementX" : 15.0, + "placementY" : 794.402, + "placementWidth" : 570.276, + "placementHeight" : 32.488, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 570.276, + "contentHeight" : 32.488, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 4.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsHeader[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsHeader[0]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 15.0, + "computedY" : 798.402, + "placementX" : 15.0, + "placementY" : 798.402, + "placementWidth" : 570.276, + "placementHeight" : 28.488, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 570.276, + "contentHeight" : 28.488, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsContact[1]", + "entityName" : "BoxedSectionsContact", + "entityKind" : "SectionNode", + "parentPath" : "BoxedSectionsRoot[0]", + "childIndex" : 1, + "depth" : 2, + "layer" : 2, + "computedX" : 15.0, + "computedY" : 768.14, + "placementX" : 15.0, + "placementY" : 768.14, + "placementWidth" : 570.276, + "placementHeight" : 19.263, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 570.276, + "contentHeight" : 19.263, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 4.0, + "right" : 0.0, + "bottom" : 4.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsContact[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsContact[1]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 15.0, + "computedY" : 772.14, + "placementX" : 15.0, + "placementY" : 772.14, + "placementWidth" : 570.276, + "placementHeight" : 11.263, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 570.276, + "contentHeight" : 11.263, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBanner_0[2]", + "entityName" : "BoxedSectionsBanner_0", + "entityKind" : "SectionNode", + "parentPath" : "BoxedSectionsRoot[0]", + "childIndex" : 2, + "depth" : 2, + "layer" : 2, + "computedX" : 15.0, + "computedY" : 734.42, + "placementX" : 15.0, + "placementY" : 734.42, + "placementWidth" : 570.276, + "placementHeight" : 22.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 570.276, + "contentHeight" : 22.72, + "margin" : { + "top" : 4.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 5.0, + "right" : 5.0, + "bottom" : 5.0, + "left" : 5.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBanner_0[2]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsBanner_0[2]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 20.0, + "computedY" : 739.42, + "placementX" : 20.0, + "placementY" : 739.42, + "placementWidth" : 560.276, + "placementHeight" : 12.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 560.276, + "contentHeight" : 12.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]", + "entityName" : "BoxedSectionsBody_0", + "entityKind" : "SectionNode", + "parentPath" : "BoxedSectionsRoot[0]", + "childIndex" : 3, + "depth" : 2, + "layer" : 2, + "computedX" : 15.0, + "computedY" : 675.235, + "placementX" : 15.0, + "placementY" : 675.235, + "placementWidth" : 219.766, + "placementHeight" : 52.185, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 219.766, + "contentHeight" : 52.185, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 4.0, + "right" : 4.0, + "bottom" : 0.0, + "left" : 4.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 19.0, + "computedY" : 710.025, + "placementX" : 19.0, + "placementY" : 710.025, + "placementWidth" : 202.986, + "placementHeight" : 11.395, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 202.986, + "contentHeight" : 11.395, + "margin" : { + "top" : 2.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 19.0, + "computedY" : 692.63, + "placementX" : 19.0, + "placementY" : 692.63, + "placementWidth" : 211.766, + "placementHeight" : 11.395, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 211.766, + "contentHeight" : 11.395, + "margin" : { + "top" : 2.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]", + "childIndex" : 2, + "depth" : 3, + "layer" : 3, + "computedX" : 19.0, + "computedY" : 675.235, + "placementX" : 19.0, + "placementY" : 675.235, + "placementWidth" : 172.482, + "placementHeight" : 11.395, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 172.482, + "contentHeight" : 11.395, + "margin" : { + "top" : 2.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + } ] +} diff --git a/src/test/resources/layout-snapshots/canonical-templates/cv-v2/regression/boxed_sections_prose_rejection_legacy_path.json b/src/test/resources/layout-snapshots/canonical-templates/cv-v2/regression/boxed_sections_prose_rejection_legacy_path.json new file mode 100644 index 00000000..c2419d70 --- /dev/null +++ b/src/test/resources/layout-snapshots/canonical-templates/cv-v2/regression/boxed_sections_prose_rejection_legacy_path.json @@ -0,0 +1,317 @@ +{ + "formatVersion" : "2.0", + "canvas" : { + "pageWidth" : 595.276, + "pageHeight" : 841.89, + "innerWidth" : 570.276, + "innerHeight" : 811.89, + "margin" : { + "top" : 15.0, + "right" : 10.0, + "bottom" : 15.0, + "left" : 15.0 + } + }, + "totalPages" : 1, + "nodes" : [ { + "path" : "BoxedSectionsRoot[0]", + "entityName" : "BoxedSectionsRoot", + "entityKind" : "ContainerNode", + "parentPath" : null, + "childIndex" : 0, + "depth" : 1, + "layer" : 1, + "computedX" : 15.0, + "computedY" : 692.63, + "placementX" : 15.0, + "placementY" : 692.63, + "placementWidth" : 570.276, + "placementHeight" : 134.26, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 570.276, + "contentHeight" : 134.26, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsHeader[0]", + "entityName" : "BoxedSectionsHeader", + "entityKind" : "SectionNode", + "parentPath" : "BoxedSectionsRoot[0]", + "childIndex" : 0, + "depth" : 2, + "layer" : 2, + "computedX" : 15.0, + "computedY" : 794.402, + "placementX" : 15.0, + "placementY" : 794.402, + "placementWidth" : 570.276, + "placementHeight" : 32.488, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 570.276, + "contentHeight" : 32.488, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 4.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsHeader[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsHeader[0]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 15.0, + "computedY" : 798.402, + "placementX" : 15.0, + "placementY" : 798.402, + "placementWidth" : 570.276, + "placementHeight" : 28.488, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 570.276, + "contentHeight" : 28.488, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsContact[1]", + "entityName" : "BoxedSectionsContact", + "entityKind" : "SectionNode", + "parentPath" : "BoxedSectionsRoot[0]", + "childIndex" : 1, + "depth" : 2, + "layer" : 2, + "computedX" : 15.0, + "computedY" : 768.14, + "placementX" : 15.0, + "placementY" : 768.14, + "placementWidth" : 570.276, + "placementHeight" : 19.263, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 570.276, + "contentHeight" : 19.263, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 4.0, + "right" : 0.0, + "bottom" : 4.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsContact[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsContact[1]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 15.0, + "computedY" : 772.14, + "placementX" : 15.0, + "placementY" : 772.14, + "placementWidth" : 570.276, + "placementHeight" : 11.263, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 570.276, + "contentHeight" : 11.263, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBanner_0[2]", + "entityName" : "BoxedSectionsBanner_0", + "entityKind" : "SectionNode", + "parentPath" : "BoxedSectionsRoot[0]", + "childIndex" : 2, + "depth" : 2, + "layer" : 2, + "computedX" : 15.0, + "computedY" : 734.42, + "placementX" : 15.0, + "placementY" : 734.42, + "placementWidth" : 570.276, + "placementHeight" : 22.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 570.276, + "contentHeight" : 22.72, + "margin" : { + "top" : 4.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 5.0, + "right" : 5.0, + "bottom" : 5.0, + "left" : 5.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBanner_0[2]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsBanner_0[2]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 20.0, + "computedY" : 739.42, + "placementX" : 20.0, + "placementY" : 739.42, + "placementWidth" : 560.276, + "placementHeight" : 12.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 560.276, + "contentHeight" : 12.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]", + "entityName" : "BoxedSectionsBody_0", + "entityKind" : "SectionNode", + "parentPath" : "BoxedSectionsRoot[0]", + "childIndex" : 3, + "depth" : 2, + "layer" : 2, + "computedX" : 15.0, + "computedY" : 692.63, + "placementX" : 15.0, + "placementY" : 692.63, + "placementWidth" : 500.66, + "placementHeight" : 34.79, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 500.66, + "contentHeight" : 34.79, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 4.0, + "right" : 4.0, + "bottom" : 0.0, + "left" : 4.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 19.0, + "computedY" : 710.025, + "placementX" : 19.0, + "placementY" : 710.025, + "placementWidth" : 492.66, + "placementHeight" : 11.395, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 492.66, + "contentHeight" : 11.395, + "margin" : { + "top" : 2.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 19.0, + "computedY" : 692.63, + "placementX" : 19.0, + "placementY" : 692.63, + "placementWidth" : 466.937, + "placementHeight" : 11.395, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 466.937, + "contentHeight" : 11.395, + "margin" : { + "top" : 2.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + } ] +} diff --git a/src/test/resources/layout-snapshots/canonical-templates/cv-v2/regression/boxed_sections_work_history_block.json b/src/test/resources/layout-snapshots/canonical-templates/cv-v2/regression/boxed_sections_work_history_block.json new file mode 100644 index 00000000..900e90d6 --- /dev/null +++ b/src/test/resources/layout-snapshots/canonical-templates/cv-v2/regression/boxed_sections_work_history_block.json @@ -0,0 +1,677 @@ +{ + "formatVersion" : "2.0", + "canvas" : { + "pageWidth" : 595.276, + "pageHeight" : 841.89, + "innerWidth" : 570.276, + "innerHeight" : 811.89, + "margin" : { + "top" : 15.0, + "right" : 10.0, + "bottom" : 15.0, + "left" : 15.0 + } + }, + "totalPages" : 1, + "nodes" : [ { + "path" : "BoxedSectionsRoot[0]", + "entityName" : "BoxedSectionsRoot", + "entityKind" : "ContainerNode", + "parentPath" : null, + "childIndex" : 0, + "depth" : 1, + "layer" : 1, + "computedX" : 15.0, + "computedY" : 629.99, + "placementX" : 15.0, + "placementY" : 629.99, + "placementWidth" : 570.276, + "placementHeight" : 196.9, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 570.276, + "contentHeight" : 196.9, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsHeader[0]", + "entityName" : "BoxedSectionsHeader", + "entityKind" : "SectionNode", + "parentPath" : "BoxedSectionsRoot[0]", + "childIndex" : 0, + "depth" : 2, + "layer" : 2, + "computedX" : 15.0, + "computedY" : 794.402, + "placementX" : 15.0, + "placementY" : 794.402, + "placementWidth" : 570.276, + "placementHeight" : 32.488, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 570.276, + "contentHeight" : 32.488, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 4.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsHeader[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsHeader[0]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 15.0, + "computedY" : 798.402, + "placementX" : 15.0, + "placementY" : 798.402, + "placementWidth" : 570.276, + "placementHeight" : 28.488, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 570.276, + "contentHeight" : 28.488, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsContact[1]", + "entityName" : "BoxedSectionsContact", + "entityKind" : "SectionNode", + "parentPath" : "BoxedSectionsRoot[0]", + "childIndex" : 1, + "depth" : 2, + "layer" : 2, + "computedX" : 15.0, + "computedY" : 768.14, + "placementX" : 15.0, + "placementY" : 768.14, + "placementWidth" : 570.276, + "placementHeight" : 19.263, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 570.276, + "contentHeight" : 19.263, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 4.0, + "right" : 0.0, + "bottom" : 4.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsContact[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsContact[1]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 15.0, + "computedY" : 772.14, + "placementX" : 15.0, + "placementY" : 772.14, + "placementWidth" : 570.276, + "placementHeight" : 11.263, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 570.276, + "contentHeight" : 11.263, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBanner_0[2]", + "entityName" : "BoxedSectionsBanner_0", + "entityKind" : "SectionNode", + "parentPath" : "BoxedSectionsRoot[0]", + "childIndex" : 2, + "depth" : 2, + "layer" : 2, + "computedX" : 15.0, + "computedY" : 734.42, + "placementX" : 15.0, + "placementY" : 734.42, + "placementWidth" : 570.276, + "placementHeight" : 22.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 570.276, + "contentHeight" : 22.72, + "margin" : { + "top" : 4.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 5.0, + "right" : 5.0, + "bottom" : 5.0, + "left" : 5.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBanner_0[2]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsBanner_0[2]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 20.0, + "computedY" : 739.42, + "placementX" : 20.0, + "placementY" : 739.42, + "placementWidth" : 560.276, + "placementHeight" : 12.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 560.276, + "contentHeight" : 12.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]", + "entityName" : "BoxedSectionsBody_0", + "entityKind" : "SectionNode", + "parentPath" : "BoxedSectionsRoot[0]", + "childIndex" : 3, + "depth" : 2, + "layer" : 2, + "computedX" : 15.0, + "computedY" : 629.99, + "placementX" : 15.0, + "placementY" : 629.99, + "placementWidth" : 570.276, + "placementHeight" : 97.43, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 570.276, + "contentHeight" : 97.43, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 4.0, + "right" : 4.0, + "bottom" : 0.0, + "left" : 4.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]/BoxedSectionsEntryHeader[0]", + "entityName" : "BoxedSectionsEntryHeader", + "entityKind" : "RowNode", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 19.0, + "computedY" : 711.23, + "placementX" : 19.0, + "placementY" : 711.23, + "placementWidth" : 562.276, + "placementHeight" : 12.19, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 562.276, + "contentHeight" : 12.19, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]/BoxedSectionsEntryHeader[0]/Title[0]", + "entityName" : "Title", + "entityKind" : "SectionNode", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]/BoxedSectionsEntryHeader[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 19.0, + "computedY" : 711.23, + "placementX" : 19.0, + "placementY" : 711.23, + "placementWidth" : 112.038, + "placementHeight" : 12.19, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 112.038, + "contentHeight" : 12.19, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]/BoxedSectionsEntryHeader[0]/Title[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]/BoxedSectionsEntryHeader[0]/Title[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 19.0, + "computedY" : 711.23, + "placementX" : 19.0, + "placementY" : 711.23, + "placementWidth" : 112.038, + "placementHeight" : 12.19, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 112.038, + "contentHeight" : 12.19, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]/BoxedSectionsEntryHeader[0]/Date[1]", + "entityName" : "Date", + "entityKind" : "SectionNode", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]/BoxedSectionsEntryHeader[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 409.259, + "computedY" : 711.76, + "placementX" : 409.259, + "placementY" : 711.76, + "placementWidth" : 172.017, + "placementHeight" : 11.66, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 172.017, + "contentHeight" : 11.66, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]/BoxedSectionsEntryHeader[0]/Date[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]/BoxedSectionsEntryHeader[0]/Date[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 409.259, + "computedY" : 711.76, + "placementX" : 409.259, + "placementY" : 711.76, + "placementWidth" : 172.017, + "placementHeight" : 11.66, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 172.017, + "contentHeight" : 11.66, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 19.0, + "computedY" : 696.1, + "placementX" : 19.0, + "placementY" : 696.1, + "placementWidth" : 68.88, + "placementHeight" : 11.13, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 68.88, + "contentHeight" : 11.13, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]", + "childIndex" : 2, + "depth" : 3, + "layer" : 3, + "computedX" : 19.0, + "computedY" : 678.705, + "placementX" : 19.0, + "placementY" : 678.705, + "placementWidth" : 186.534, + "placementHeight" : 11.395, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 186.534, + "contentHeight" : 11.395, + "margin" : { + "top" : 2.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]/BoxedSectionsEntryHeader[3]", + "entityName" : "BoxedSectionsEntryHeader", + "entityKind" : "RowNode", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]", + "childIndex" : 3, + "depth" : 3, + "layer" : 3, + "computedX" : 19.0, + "computedY" : 662.515, + "placementX" : 19.0, + "placementY" : 662.515, + "placementWidth" : 562.276, + "placementHeight" : 12.19, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 562.276, + "contentHeight" : 12.19, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]/BoxedSectionsEntryHeader[3]/Title[0]", + "entityName" : "Title", + "entityKind" : "SectionNode", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]/BoxedSectionsEntryHeader[3]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 19.0, + "computedY" : 662.515, + "placementX" : 19.0, + "placementY" : 662.515, + "placementWidth" : 80.914, + "placementHeight" : 12.19, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 80.914, + "contentHeight" : 12.19, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]/BoxedSectionsEntryHeader[3]/Title[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]/BoxedSectionsEntryHeader[3]/Title[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 19.0, + "computedY" : 662.515, + "placementX" : 19.0, + "placementY" : 662.515, + "placementWidth" : 80.914, + "placementHeight" : 12.19, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 80.914, + "contentHeight" : 12.19, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]/BoxedSectionsEntryHeader[3]/Date[1]", + "entityName" : "Date", + "entityKind" : "SectionNode", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]/BoxedSectionsEntryHeader[3]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 409.259, + "computedY" : 663.045, + "placementX" : 409.259, + "placementY" : 663.045, + "placementWidth" : 172.017, + "placementHeight" : 11.66, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 172.017, + "contentHeight" : 11.66, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]/BoxedSectionsEntryHeader[3]/Date[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]/BoxedSectionsEntryHeader[3]/Date[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 409.259, + "computedY" : 663.045, + "placementX" : 409.259, + "placementY" : 663.045, + "placementWidth" : 172.017, + "placementHeight" : 11.66, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 172.017, + "contentHeight" : 11.66, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]/ParagraphNode[4]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]", + "childIndex" : 4, + "depth" : 3, + "layer" : 3, + "computedX" : 19.0, + "computedY" : 647.385, + "placementX" : 19.0, + "placementY" : 647.385, + "placementWidth" : 56.448, + "placementHeight" : 11.13, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 56.448, + "contentHeight" : 11.13, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]/ParagraphNode[5]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsBody_0[3]", + "childIndex" : 5, + "depth" : 3, + "layer" : 3, + "computedX" : 19.0, + "computedY" : 629.99, + "placementX" : 19.0, + "placementY" : 629.99, + "placementWidth" : 180.05, + "placementHeight" : 11.395, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 180.05, + "contentHeight" : 11.395, + "margin" : { + "top" : 2.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + } ] +} diff --git a/src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_boxed_sections.json b/src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_boxed_sections.json index 851bcc4d..5c51975a 100644 --- a/src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_boxed_sections.json +++ b/src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_boxed_sections.json @@ -22,15 +22,15 @@ "depth" : 1, "layer" : 1, "computedX" : 15.0, - "computedY" : 234.18, + "computedY" : 221.65, "placementX" : 15.0, - "placementY" : 234.18, + "placementY" : 221.65, "placementWidth" : 570.276, - "placementHeight" : 592.71, + "placementHeight" : 605.24, "startPage" : 0, "endPage" : 0, "contentWidth" : 570.276, - "contentHeight" : 592.71, + "contentHeight" : 605.24, "margin" : { "top" : 0.0, "right" : 0.0, @@ -892,15 +892,15 @@ "depth" : 2, "layer" : 2, "computedX" : 15.0, - "computedY" : 309.69, + "computedY" : 297.16, "placementX" : 15.0, - "placementY" : 309.69, + "placementY" : 297.16, "placementWidth" : 570.276, - "placementHeight" : 84.9, + "placementHeight" : 97.43, "startPage" : 0, "endPage" : 0, "contentWidth" : 570.276, - "contentHeight" : 84.9, + "contentHeight" : 97.43, "margin" : { "top" : 0.0, "right" : 0.0, @@ -922,15 +922,15 @@ "depth" : 3, "layer" : 3, "computedX" : 19.0, - "computedY" : 367.27, + "computedY" : 378.4, "placementX" : 19.0, - "placementY" : 367.27, + "placementY" : 378.4, "placementWidth" : 562.276, - "placementHeight" : 23.32, + "placementHeight" : 12.19, "startPage" : 0, "endPage" : 0, "contentWidth" : 562.276, - "contentHeight" : 23.32, + "contentHeight" : 12.19, "margin" : { "top" : 0.0, "right" : 0.0, @@ -1012,15 +1012,15 @@ "depth" : 4, "layer" : 4, "computedX" : 409.259, - "computedY" : 367.27, + "computedY" : 378.93, "placementX" : 409.259, - "placementY" : 367.27, + "placementY" : 378.93, "placementWidth" : 172.017, - "placementHeight" : 23.32, + "placementHeight" : 11.66, "startPage" : 0, "endPage" : 0, "contentWidth" : 172.017, - "contentHeight" : 23.32, + "contentHeight" : 11.66, "margin" : { "top" : 0.0, "right" : 0.0, @@ -1042,15 +1042,15 @@ "depth" : 5, "layer" : 5, "computedX" : 409.259, - "computedY" : 367.27, + "computedY" : 378.93, "placementX" : 409.259, - "placementY" : 367.27, + "placementY" : 378.93, "placementWidth" : 172.017, - "placementHeight" : 23.32, + "placementHeight" : 11.66, "startPage" : 0, "endPage" : 0, "contentWidth" : 172.017, - "contentHeight" : 23.32, + "contentHeight" : 11.66, "margin" : { "top" : 0.0, "right" : 0.0, @@ -1072,9 +1072,9 @@ "depth" : 3, "layer" : 3, "computedX" : 19.0, - "computedY" : 352.14, + "computedY" : 363.27, "placementX" : 19.0, - "placementY" : 352.14, + "placementY" : 363.27, "placementWidth" : 68.88, "placementHeight" : 11.13, "startPage" : 0, @@ -1094,23 +1094,53 @@ "left" : 0.0 } }, { - "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_4[11]/BoxedSectionsEntryHeader[2]", + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_4[11]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsBody_4[11]", + "childIndex" : 2, + "depth" : 3, + "layer" : 3, + "computedX" : 19.0, + "computedY" : 345.875, + "placementX" : 19.0, + "placementY" : 345.875, + "placementWidth" : 113.511, + "placementHeight" : 11.395, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 113.511, + "contentHeight" : 11.395, + "margin" : { + "top" : 2.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_4[11]/BoxedSectionsEntryHeader[3]", "entityName" : "BoxedSectionsEntryHeader", "entityKind" : "RowNode", "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsBody_4[11]", - "childIndex" : 2, + "childIndex" : 3, "depth" : 3, "layer" : 3, "computedX" : 19.0, - "computedY" : 324.82, + "computedY" : 329.685, "placementX" : 19.0, - "placementY" : 324.82, + "placementY" : 329.685, "placementWidth" : 562.276, - "placementHeight" : 23.32, + "placementHeight" : 12.19, "startPage" : 0, "endPage" : 0, "contentWidth" : 562.276, - "contentHeight" : 23.32, + "contentHeight" : 12.19, "margin" : { "top" : 0.0, "right" : 0.0, @@ -1124,17 +1154,17 @@ "left" : 0.0 } }, { - "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_4[11]/BoxedSectionsEntryHeader[2]/Title[0]", + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_4[11]/BoxedSectionsEntryHeader[3]/Title[0]", "entityName" : "Title", "entityKind" : "SectionNode", - "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsBody_4[11]/BoxedSectionsEntryHeader[2]", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsBody_4[11]/BoxedSectionsEntryHeader[3]", "childIndex" : 0, "depth" : 4, "layer" : 4, "computedX" : 19.0, - "computedY" : 335.95, + "computedY" : 329.685, "placementX" : 19.0, - "placementY" : 335.95, + "placementY" : 329.685, "placementWidth" : 80.914, "placementHeight" : 12.19, "startPage" : 0, @@ -1154,17 +1184,17 @@ "left" : 0.0 } }, { - "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_4[11]/BoxedSectionsEntryHeader[2]/Title[0]/ParagraphNode[0]", + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_4[11]/BoxedSectionsEntryHeader[3]/Title[0]/ParagraphNode[0]", "entityName" : null, "entityKind" : "ParagraphNode", - "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsBody_4[11]/BoxedSectionsEntryHeader[2]/Title[0]", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsBody_4[11]/BoxedSectionsEntryHeader[3]/Title[0]", "childIndex" : 0, "depth" : 5, "layer" : 5, "computedX" : 19.0, - "computedY" : 335.95, + "computedY" : 329.685, "placementX" : 19.0, - "placementY" : 335.95, + "placementY" : 329.685, "placementWidth" : 80.914, "placementHeight" : 12.19, "startPage" : 0, @@ -1184,23 +1214,23 @@ "left" : 0.0 } }, { - "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_4[11]/BoxedSectionsEntryHeader[2]/Date[1]", + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_4[11]/BoxedSectionsEntryHeader[3]/Date[1]", "entityName" : "Date", "entityKind" : "SectionNode", - "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsBody_4[11]/BoxedSectionsEntryHeader[2]", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsBody_4[11]/BoxedSectionsEntryHeader[3]", "childIndex" : 1, "depth" : 4, "layer" : 4, "computedX" : 409.259, - "computedY" : 324.82, + "computedY" : 330.215, "placementX" : 409.259, - "placementY" : 324.82, + "placementY" : 330.215, "placementWidth" : 172.017, - "placementHeight" : 23.32, + "placementHeight" : 11.66, "startPage" : 0, "endPage" : 0, "contentWidth" : 172.017, - "contentHeight" : 23.32, + "contentHeight" : 11.66, "margin" : { "top" : 0.0, "right" : 0.0, @@ -1214,23 +1244,23 @@ "left" : 0.0 } }, { - "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_4[11]/BoxedSectionsEntryHeader[2]/Date[1]/ParagraphNode[0]", + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_4[11]/BoxedSectionsEntryHeader[3]/Date[1]/ParagraphNode[0]", "entityName" : null, "entityKind" : "ParagraphNode", - "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsBody_4[11]/BoxedSectionsEntryHeader[2]/Date[1]", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsBody_4[11]/BoxedSectionsEntryHeader[3]/Date[1]", "childIndex" : 0, "depth" : 5, "layer" : 5, "computedX" : 409.259, - "computedY" : 324.82, + "computedY" : 330.215, "placementX" : 409.259, - "placementY" : 324.82, + "placementY" : 330.215, "placementWidth" : 172.017, - "placementHeight" : 23.32, + "placementHeight" : 11.66, "startPage" : 0, "endPage" : 0, "contentWidth" : 172.017, - "contentHeight" : 23.32, + "contentHeight" : 11.66, "margin" : { "top" : 0.0, "right" : 0.0, @@ -1244,17 +1274,17 @@ "left" : 0.0 } }, { - "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_4[11]/ParagraphNode[3]", + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_4[11]/ParagraphNode[4]", "entityName" : null, "entityKind" : "ParagraphNode", "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsBody_4[11]", - "childIndex" : 3, + "childIndex" : 4, "depth" : 3, "layer" : 3, "computedX" : 19.0, - "computedY" : 309.69, + "computedY" : 314.555, "placementX" : 19.0, - "placementY" : 309.69, + "placementY" : 314.555, "placementWidth" : 56.448, "placementHeight" : 11.13, "startPage" : 0, @@ -1273,6 +1303,36 @@ "bottom" : 0.0, "left" : 0.0 } + }, { + "path" : "BoxedSectionsRoot[0]/BoxedSectionsBody_4[11]/ParagraphNode[5]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "BoxedSectionsRoot[0]/BoxedSectionsBody_4[11]", + "childIndex" : 5, + "depth" : 3, + "layer" : 3, + "computedX" : 19.0, + "computedY" : 297.16, + "placementX" : 19.0, + "placementY" : 297.16, + "placementWidth" : 180.05, + "placementHeight" : 11.395, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 180.05, + "contentHeight" : 11.395, + "margin" : { + "top" : 2.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } }, { "path" : "BoxedSectionsRoot[0]/BoxedSectionsBanner_5[12]", "entityName" : "BoxedSectionsBanner_5", @@ -1282,9 +1342,9 @@ "depth" : 2, "layer" : 2, "computedX" : 15.0, - "computedY" : 275.97, + "computedY" : 263.44, "placementX" : 15.0, - "placementY" : 275.97, + "placementY" : 263.44, "placementWidth" : 570.276, "placementHeight" : 22.72, "startPage" : 0, @@ -1312,9 +1372,9 @@ "depth" : 3, "layer" : 3, "computedX" : 20.0, - "computedY" : 280.97, + "computedY" : 268.44, "placementX" : 20.0, - "placementY" : 280.97, + "placementY" : 268.44, "placementWidth" : 560.276, "placementHeight" : 12.72, "startPage" : 0, @@ -1342,14 +1402,14 @@ "depth" : 2, "layer" : 2, "computedX" : 15.0, - "computedY" : 234.18, + "computedY" : 221.65, "placementX" : 15.0, - "placementY" : 234.18, - "placementWidth" : 295.085, + "placementY" : 221.65, + "placementWidth" : 297.76, "placementHeight" : 34.79, "startPage" : 0, "endPage" : 0, - "contentWidth" : 295.085, + "contentWidth" : 297.76, "contentHeight" : 34.79, "margin" : { "top" : 0.0, @@ -1372,14 +1432,14 @@ "depth" : 3, "layer" : 3, "computedX" : 19.0, - "computedY" : 251.575, + "computedY" : 239.045, "placementX" : 19.0, - "placementY" : 251.575, - "placementWidth" : 287.085, + "placementY" : 239.045, + "placementWidth" : 289.76, "placementHeight" : 11.395, "startPage" : 0, "endPage" : 0, - "contentWidth" : 287.085, + "contentWidth" : 289.76, "contentHeight" : 11.395, "margin" : { "top" : 2.0, @@ -1402,14 +1462,14 @@ "depth" : 3, "layer" : 3, "computedX" : 19.0, - "computedY" : 234.18, + "computedY" : 221.65, "placementX" : 19.0, - "placementY" : 234.18, - "placementWidth" : 273.962, + "placementY" : 221.65, + "placementWidth" : 276.499, "placementHeight" : 11.395, "startPage" : 0, "endPage" : 0, - "contentWidth" : 273.962, + "contentWidth" : 276.499, "contentHeight" : 11.395, "margin" : { "top" : 2.0, diff --git a/src/test/resources/visual-baselines/cv-v2/boxed_sections-page-0.png b/src/test/resources/visual-baselines/cv-v2/boxed_sections-page-0.png index 6722c836..c3651be8 100644 Binary files a/src/test/resources/visual-baselines/cv-v2/boxed_sections-page-0.png and b/src/test/resources/visual-baselines/cv-v2/boxed_sections-page-0.png differ