diff --git a/CHANGELOG.md b/CHANGELOG.md index d7f869e47..f04c042f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -99,14 +99,20 @@ follow semantic versioning; release dates are ISO 8601. a project title is a link when its entry carries one, and the monogram is drawn from the name's own initials rather than a field of its own — a document states its name once, and a monogram that could disagree with it would be a second place to keep - true. A link in the contact block is set smaller than the channels above it, because - a URL is one long token that cannot be broken and is the line that outgrows that - column first; it takes the mark of the network it points at, or a globe. Like its - siblings it holds one page: the body is a single atomic row, so a longer CV raises + true. **A link in the contact block is drawn as its own label with the address behind + it**, which is where the preset departs from the sheet it ports: writing the URL out + makes that row as wide as whatever the reader's profile happens to be called — long + enough that the published design sets it smaller and nudged in, on an axis of its + own. Stating `Link("LinkedIn", "https://…")` puts the four rows on one axis at one + size for every document. The departure is exactly measured — two of 154 nodes narrow, + 1 743 of 2 173 720 pixels change, and nothing moves vertically — and both baselines + were recorded with it. A link takes the mark of the network it points at, or a globe. + Like its siblings it holds one page: the body is a single atomic row, so a longer CV raises `AtomicNodeTooLargeException` rather than flowing or dropping entries. Guarded by a smoke test (including the unknown-mark data error, an entry with no mark, an identity with no links, a document with nothing but an identity, the monogram, the link - targets and the one-page limit), an exact layout snapshot and a pixel-parity gate; + targets, the four contact rows sharing one axis and the one-page limit), an exact + layout snapshot and a pixel-parity gate; the examples showcase gains `cv-terracotta-rail-v2`. - **The first invoice preset that paginates what it ports: `LumaStudioInvoice`.** A diff --git a/assets/readme/examples/cv-terracotta-rail-v2.pdf b/assets/readme/examples/cv-terracotta-rail-v2.pdf index f598c897e..acd667adf 100644 Binary files a/assets/readme/examples/cv-terracotta-rail-v2.pdf and b/assets/readme/examples/cv-terracotta-rail-v2.pdf differ diff --git a/examples/src/main/java/com/demcha/examples/support/TerracottaRailSampleData.java b/examples/src/main/java/com/demcha/examples/support/TerracottaRailSampleData.java index 2a6490270..b4f2d7885 100644 --- a/examples/src/main/java/com/demcha/examples/support/TerracottaRailSampleData.java +++ b/examples/src/main/java/com/demcha/examples/support/TerracottaRailSampleData.java @@ -62,7 +62,7 @@ private static CvIdentity identity() { "SENIOR ARCHITECT", new Contact("+44 7700 900123", "oliver.bennett@email.com", "Bristol, United Kingdom"), - List.of(new Link("linkedin.com/in/oliverbennett-architect", + List.of(new Link("LinkedIn", "https://linkedin.com/in/oliverbennett-architect")), Optional.empty()); } diff --git a/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/TerracottaRailFixtures.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/TerracottaRailFixtures.java index 6b8271bd5..4852b5601 100644 --- a/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/TerracottaRailFixtures.java +++ b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/TerracottaRailFixtures.java @@ -60,7 +60,7 @@ static CvIdentity identity() { "SENIOR ARCHITECT", new Contact("+44 7700 900123", "oliver.bennett@email.com", "Bristol, United Kingdom"), - List.of(new Link("linkedin.com/in/oliverbennett-architect", + List.of(new Link("LinkedIn", "https://linkedin.com/in/oliverbennett-architect")), Optional.empty()); } diff --git a/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/TerracottaRailSmokeTest.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/TerracottaRailSmokeTest.java index d443008f6..1cbf9fc03 100644 --- a/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/TerracottaRailSmokeTest.java +++ b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/TerracottaRailSmokeTest.java @@ -142,6 +142,37 @@ void contactChannelsAndLinksAreClickable() throws Exception { .contains("https://linkedin.com/in/oliverbennett-architect"); } + @Test + void aLinkShowsItsLabelAndHidesItsAddress() throws Exception { + // The row draws the label, so its width is the same whatever the + // profile behind it is called; the address is reachable, not written. + byte[] pdfBytes = render(TerracottaRailFixtures.canonicalCv()); + assertThat(textOf(pdfBytes)) + .contains("LinkedIn") + .doesNotContain("linkedin.com/in/oliverbennett-architect"); + assertThat(linkTargets(pdfBytes)) + .contains("https://linkedin.com/in/oliverbennett-architect"); + } + + @Test + void everyContactRowStartsOnTheSameAxis() throws Exception { + // Four rows, one mark width and one gap: a link that was set smaller + // or nudged in would put its text on an axis of its own. + try (DocumentSession session = GraphCompose.document() + .pageSize(DocumentPageSize.A4) + .margin(0f, 0f, 0f, 0f) + .create()) { + TerracottaRail.create().compose(session, TerracottaRailFixtures.canonicalCv()); + List heights = session.layoutSnapshot().nodes().stream() + .filter(node -> node.entityName().startsWith("Contact_")) + .map(node -> node.placementHeight()) + .toList(); + assertThat(heights).hasSize(4); + assertThat(heights).allMatch(height -> Math.abs(height - heights.get(0)) < 0.01, + "every contact row is as tall as the first"); + } + } + @Test void aProjectTitleBecomesALinkWhenItsEntryCarriesOne() throws Exception { EntriesSection linked = new EntriesSection("SELECTED PROJECTS", List.of( diff --git a/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/terracotta_rail_layout.json b/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/terracotta_rail_layout.json index 4e6f57818..fef4c7d24 100644 --- a/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/terracotta_rail_layout.json +++ b/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/terracotta_rail_layout.json @@ -175,11 +175,11 @@ "computedY" : 684.99, "placementX" : 25.4, "placementY" : 684.99, - "placementWidth" : 138.313, + "placementWidth" : 113.88, "placementHeight" : 69.8, "startPage" : 0, "endPage" : 0, - "contentWidth" : 138.313, + "contentWidth" : 113.88, "contentHeight" : 69.8, "margin" : { "top" : 0.0, @@ -295,11 +295,11 @@ "computedY" : 689.49, "placementX" : 25.4, "placementY" : 689.49, - "placementWidth" : 138.313, + "placementWidth" : 49.355, "placementHeight" : 12.95, "startPage" : 0, "endPage" : 0, - "contentWidth" : 138.313, + "contentWidth" : 49.355, "contentHeight" : 12.95, "margin" : { "top" : 0.0, diff --git a/qa/src/test/resources/visual-baselines/cv-v2-layered/terracotta_rail-page-0.png b/qa/src/test/resources/visual-baselines/cv-v2-layered/terracotta_rail-page-0.png index 67030d567..ee8cfdaef 100644 Binary files a/qa/src/test/resources/visual-baselines/cv-v2-layered/terracotta_rail-page-0.png and b/qa/src/test/resources/visual-baselines/cv-v2-layered/terracotta_rail-page-0.png differ diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TerracottaRail.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TerracottaRail.java index 6b20a5834..06dd1b06e 100644 --- a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TerracottaRail.java +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TerracottaRail.java @@ -78,8 +78,9 @@ * *

The email, the phone and each link are reachable from the PDF, with the * {@code mailto:} and {@code tel:} targets built from the values. A link is - * set smaller than a channel above it, because a URL is one long token that - * cannot be broken and is the line that outgrows this column first; it takes + * drawn as its own label with the address behind it — {@code Link("LinkedIn", + * "https://…")} sets the word and links the URL — so the four contact rows sit + * on one axis at one size whatever a profile happens to be called. It takes * the mark of the network it points at, or a globe.

* *

Fonts

diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TerracottaRailAside.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TerracottaRailAside.java index 3a8749596..b9a8aa33e 100644 --- a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TerracottaRailAside.java +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TerracottaRailAside.java @@ -25,7 +25,6 @@ import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.INFO_LINE_GAP; import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.INFO_WEIGHTS; import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.INK; -import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.LINK_SCALE; import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.MONOGRAM_RULE_TO_CONTACT_GAP; import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.MONOGRAM_RULE_WIDTH; import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.MONOGRAM_SIZE; @@ -125,12 +124,14 @@ private static void appendInitial(StringBuilder out, String part) { // -- the channels ------------------------------------------------------ /** - * The contact block: each channel behind its mark, and the links beneath - * them. + * The contact block: the three channels and a row per link, all on one + * axis behind their marks. * - *

A link is set smaller than a channel. A URL is one long token that - * cannot be broken, so it is the line that outgrows this column first, - * while a phone, an address or a city is short enough at full size.

+ *

A link shows its own label and carries the address behind it, which + * is what keeps the rows the same. Writing the URL out would make that row + * as wide as whatever the reader's profile happens to be called — long + * enough to need setting smaller than the rows above it, and a different + * width for every document.

*/ private static void renderContact(SectionBuilder side, CvIdentity identity) { side.addSection("Contact", block -> { @@ -139,28 +140,28 @@ private static void renderContact(SectionBuilder side, CvIdentity identity) { // channels are always drawn; only the links are optional. Contact contact = identity.contact(); channel(block, 0, TerracottaRailIcons.EMAIL, contact.email(), - "mailto:" + contact.email(), false); + "mailto:" + contact.email()); channel(block, 1, TerracottaRailIcons.PHONE, contact.phone(), - telUri(contact.phone()), false); - channel(block, 2, TerracottaRailIcons.LOCATION, contact.address(), null, false); + telUri(contact.phone())); + channel(block, 2, TerracottaRailIcons.LOCATION, contact.address(), null); int index = 3; for (Link link : identity.links()) { - channel(block, index++, markFor(link), link.label(), link.url(), true); + channel(block, index++, markFor(link), link.label(), link.url()); } }); } private static void channel(SectionBuilder block, int index, String token, String value, - String href, boolean compact) { + String href) { block.addParagraph(p -> { p.name("Contact_" + index); inlineIcon(p, token, TerracottaRailIcons.CONTACT_SIZE); - p.inlineText(compact ? " " : " "); - double size = compact ? DETAIL_SIZE * LINK_SCALE : DETAIL_SIZE; + p.inlineText(" "); if (href == null || href.isBlank()) { - p.inlineText(value, text(size, INK, false)); + p.inlineText(value, text(DETAIL_SIZE, INK, false)); } else { - p.inlineText(value, text(size, INK, false), new DocumentLinkOptions(href)); + p.inlineText(value, text(DETAIL_SIZE, INK, false), + new DocumentLinkOptions(href)); } p.margin(0f, 0f, (float) CONTACT_ROW_GAP, 0f); }); diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TerracottaRailStyles.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TerracottaRailStyles.java index 83e5a3960..89be2b0a5 100644 --- a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TerracottaRailStyles.java +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TerracottaRailStyles.java @@ -56,13 +56,6 @@ private TerracottaRailStyles() { static final double NAME_SIZE = 28.0; static final double SUBTITLE_SIZE = 13.0; - /** - * How much smaller a link is set than the channels above it. A URL is one - * long token that cannot be broken, so it is the line that outgrows this - * sidebar first. - */ - static final double LINK_SCALE = 0.85; - // -- palette ---------------------------------------------------------- static final DocumentColor ACCENT = DocumentColor.rgb(201, 74, 41);