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
7 changes: 4 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ follow semantic versioning; release dates are ISO 8601.
terracotta square and no dash under its heading, the other a disc and a dash, which
is how the sheet tells two lists of one-liners apart. Each fact and each project
takes the mark its entry names in `CvEntry.icon()` from this preset's own vocabulary,
a project title is a link when its entry carries one, and the monogram is drawn from
every title it draws — a role, a project, a degree, a credential — 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 drawn as its own label with the address behind
Expand All @@ -111,8 +112,8 @@ follow semantic versioning; release dates are ISO 8601.
`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, the four contact rows sharing one axis and the one-page limit), an exact
layout snapshot and a pixel-parity gate;
targets on every kind of title, 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
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,46 @@ void everyContactRowStartsOnTheSameAxis() throws Exception {
}
}

@Test
void everyTitleBecomesALinkWhenItsEntryCarriesOne() throws Exception {
// A role, a degree and a credential, each pointing somewhere. None of
// it moves a pixel or a layout node, so this is the only thing that
// would notice the annotations going missing.
List<CvDocument.Placement> placements = new ArrayList<>();
for (CvDocument.Placement placement : TerracottaRailFixtures.canonicalCv().placements()) {
placements.add(switch (placement.section().title()) {
case "PROFESSIONAL EXPERIENCE" -> new CvDocument.Placement(Slot.MAIN,
new EntriesSection("PROFESSIONAL EXPERIENCE", List.of(
CvEntry.builder("Senior Architect")
.subtitle("Northline Studio, Bristol, UK")
.date("2021")
.link("https://example.test/northline")
.body("Lead design packages.")
.build())));
case "EDUCATION" -> new CvDocument.Placement(Slot.MAIN,
new EntriesSection("EDUCATION", List.of(
CvEntry.builder("MArch Architecture")
.subtitle("University of Sheffield")
.date("2014")
.link("https://example.test/sheffield")
.build())));
case "CERTIFICATIONS" -> new CvDocument.Placement(Slot.SIDEBAR,
new EntriesSection("CERTIFICATIONS", List.of(
CvEntry.builder("ARB Registered Architect")
.link("https://example.test/arb")
.build())));
default -> placement;
});
}

List<String> targets = linkTargets(
render(new CvDocument(TerracottaRailFixtures.identity(), placements)));
assertThat(targets)
.contains("https://example.test/northline")
.contains("https://example.test/sheffield")
.contains("https://example.test/arb");
}

@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 @@ -72,9 +72,15 @@
* {@code CvEntry.icon()}, from this preset's own vocabulary —
* {@code globe}, {@code badge}, {@code clock}, {@code building},
* {@code hotel}, {@code house} — and an unknown token is reported as a data
* error naming the set. An entry with no token is drawn without a mark, and
* a project title becomes a link when its entry carries
* {@code CvEntry.link()}.</p>
* error naming the set. An entry with no token is drawn without a mark.</p>
*
* <p>Every title the preset draws — a role, a project, a degree, a
* credential — becomes a link when its entry carries {@code CvEntry.link()}.
* It costs the layout nothing, because a link is an annotation rather than
* ink, so a linked title and a plain one are the same line. The closing facts
* are the exception: their bold line is a label for the values under it —
* "Languages:", "Availability:" — rather than the name of something a reader
* could open.</p>
*
* <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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,12 +213,12 @@ private static void renderBulletedSkills(SectionBuilder side, SkillsSection sect
String name = prefix + "_" + index++;
if (discBullet) {
bulletLine(block, name, p -> p.dot(3.0, ACCENT), skill.name(),
INK, DETAIL_SIZE);
INK, DETAIL_SIZE, null);
} else {
bulletLine(block, name,
p -> inlineIcon(p, TerracottaRailIcons.SQUARE,
TerracottaRailIcons.BULLET_SIZE),
skill.name(), INK, DETAIL_SIZE);
skill.name(), INK, DETAIL_SIZE, null);
}
}
}
Expand All @@ -232,8 +232,9 @@ private static void renderCertifications(SectionBuilder side, EntriesSection sec
headingWithDash(block, section.title(), SIDEBAR_HEADING_SPACER, SIDEBAR_DASH_WIDTH);
List<CvEntry> entries = section.entries();
for (int index = 0; index < entries.size(); index++) {
CvEntry entry = entries.get(index);
bulletLine(block, "Certification_" + index, p -> p.dot(3.0, ACCENT),
entries.get(index).title(), INK, DETAIL_SIZE);
entry.title(), INK, DETAIL_SIZE, entry.link());
}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ private static void renderExperience(SectionBuilder main, EntriesSection experie
body.padding(0f, 0f, last ? 0f : (float) ENTRY_GAP, (float) ENTRY_INDENT);
railedLine(body, "Role", index,
titleAndDate("RoleTable_" + index, entry.title(), entry.date(),
ENTRY_WIDTH, ROLE_PERIOD_SHARE));
ENTRY_WIDTH, ROLE_PERIOD_SHARE, entry.link()));
body.addParagraph(p -> p
.name("Employer_" + index)
.text(entry.subtitle())
Expand Down Expand Up @@ -301,7 +301,8 @@ private static void renderEducation(SectionBuilder main, EntriesSection educatio
(float) ENTRY_INDENT);
railedLine(body, "Edu", index,
titleAndDate("EduTable_" + index, entry.title(), entry.date(),
EDUCATION_ENTRY_WIDTH, EDUCATION_PERIOD_SHARE));
EDUCATION_ENTRY_WIDTH, EDUCATION_PERIOD_SHARE,
entry.link()));
body.addParagraph(p -> p
.name("Institution_" + index)
.text(entry.subtitle())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.demcha.compose.document.dsl.RowBuilder;
import com.demcha.compose.document.dsl.SectionBuilder;
import com.demcha.compose.document.dsl.TableBuilder;
import com.demcha.compose.document.node.DocumentLinkOptions;
import com.demcha.compose.document.node.DocumentNode;
import com.demcha.compose.document.node.InlineImageAlignment;
import com.demcha.compose.document.node.LayerAlign;
Expand Down Expand Up @@ -194,10 +195,30 @@ static void railedLine(SectionBuilder body, String prefix, int index, DocumentNo
* — it spans the cell and its columns lay out. Both cells hold a
* paragraph, because a table inside a row cell draws only leaf
* content.</p>
*
* <p>The title carries the entry's link when it has one. That costs the
* layout nothing — a link is an annotation rather than ink — so a linked
* title and a plain one are the same line.</p>
*
* @param name the node name this line's parts are built from
* @param title the entry's title
* @param date the date, set flush right
* @param width the line's width
* @param dateShare how much of that width the date takes
* @param link the target the title points at, blank for none
* @return the line node
*/
static DocumentNode titleAndDate(String name, String title, String date,
double width, double dateShare) {
double width, double dateShare, String link) {
double dateColumn = width * dateShare;
ParagraphBuilder titleCell = new ParagraphBuilder()
.name(name + "_Title")
.text(title)
.lineSpacing(0)
.textStyle(text(ITEM_TITLE_SIZE, INK, true));
if (link != null && !link.isBlank()) {
titleCell.link(new DocumentLinkOptions(link));
}
return new TableBuilder()
.name(name)
.width(width)
Expand All @@ -209,12 +230,7 @@ static DocumentNode titleAndDate(String name, String title, String date,
.stroke(NO_BORDER)
.build())
.rowCells(
DocumentTableCell.node(new ParagraphBuilder()
.name(name + "_Title")
.text(title)
.lineSpacing(0)
.textStyle(text(ITEM_TITLE_SIZE, INK, true))
.build()),
DocumentTableCell.node(titleCell.build()),
DocumentTableCell.node(new ParagraphBuilder()
.name(name + "_Period")
.text(date)
Expand Down Expand Up @@ -246,14 +262,21 @@ static void layeredRow(SectionBuilder parent, String name, double marginTop,
.layer(layer.build(), LayerAlign.TOP_LEFT, 0));
}

/** A bullet line: a mark, a gap, and the item beside it. */
/**
* A bullet line: a mark, a gap, and the item beside it — carrying a link
* when one is given.
*/
static void bulletLine(SectionBuilder block, String name, Consumer<ParagraphBuilder> mark,
String item, DocumentColor color, double size) {
String item, DocumentColor color, double size, String link) {
block.addParagraph(p -> {
p.name(name);
mark.accept(p);
p.inlineText(" ");
p.inlineText(item, text(size, color, false));
if (link == null || link.isBlank()) {
p.inlineText(item, text(size, color, false));
} else {
p.inlineText(item, text(size, color, false), new DocumentLinkOptions(link));
}
p.margin(0f, 0f, (float) BULLET_ROW_GAP, 0f);
});
}
Expand Down
Loading