Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Binary file modified assets/readme/examples/cv-terracotta-rail-v2.pdf
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Double> 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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,9 @@
*
* <p>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.</p>
*
* <h2>Fonts</h2>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
*
* <p>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.</p>
* <p>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.</p>
*/
private static void renderContact(SectionBuilder side, CvIdentity identity) {
side.addSection("Contact", block -> {
Expand All @@ -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);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading