diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e1da015..6adf06ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -407,6 +407,19 @@ Entries land here as they merge. ### Internal +- **CV / cover-letter template icons moved from PNG to recolorable SVG.** + The bundled contact / social glyphs (phone, email, location, website, + LinkedIn, GitHub, …) and the sidebar-portrait avatar now ship as SVG + instead of raster PNG. A new internal `SvgGlyph` helper flattens an icon's + filled layers into one outline that the presets fill with each template's + own accent colour via `rich.shape(...)` — so one bundled glyph recolours + per template with no per-template copies, and the icons stay crisp at any + zoom. The sidebar-portrait avatar is a swappable SVG placeholder. This + shrinks the bundled `templates/cv` assets from ~717 KB to ~133 KB (the + 431 KB `portrait.png` alone becomes a ~4 KB SVG), trimming the published + jar. No public API change; the CV / cover-letter presets render the same + layout (visual baselines refreshed for the new glyphs; the sidebar-portrait + layout snapshot updated for the vector avatar). - **Benchmark suite cleanup (not shipped).** Removed three redundant benchmark mains: `FullCvBenchmark` (superseded by the JMH `TemplateCvJmhBenchmark`), `GraphComposeBenchmark` (early-engine relic diff --git a/src/main/java/com/demcha/compose/document/templates/coverletter/v2/presets/TimelineMinimalLetter.java b/src/main/java/com/demcha/compose/document/templates/coverletter/v2/presets/TimelineMinimalLetter.java index 1ecb3fba..98a28495 100644 --- a/src/main/java/com/demcha/compose/document/templates/coverletter/v2/presets/TimelineMinimalLetter.java +++ b/src/main/java/com/demcha/compose/document/templates/coverletter/v2/presets/TimelineMinimalLetter.java @@ -3,10 +3,10 @@ import com.demcha.compose.document.api.DocumentSession; import com.demcha.compose.document.dsl.PageFlowBuilder; import com.demcha.compose.document.dsl.SectionBuilder; -import com.demcha.compose.document.image.DocumentImageData; import com.demcha.compose.document.node.DocumentLinkOptions; import com.demcha.compose.document.node.InlineImageAlignment; import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.DocumentColor; import com.demcha.compose.document.style.DocumentInsets; import com.demcha.compose.document.style.DocumentTextDecoration; import com.demcha.compose.document.style.DocumentTextStyle; @@ -19,19 +19,16 @@ import com.demcha.compose.document.templates.cv.v2.data.CvIdentity; import com.demcha.compose.document.templates.cv.v2.data.CvLink; import com.demcha.compose.document.templates.cv.v2.theme.CvTheme; +import com.demcha.compose.document.templates.cv.v2.widgets.SvgGlyph; -import java.io.IOException; -import java.io.InputStream; -import java.io.UncheckedIOException; import java.util.*; -import java.util.concurrent.ConcurrentHashMap; /** * v2 cover-letter pair for the {@code TimelineMinimal} CV preset. * *

Reproduces the CV's masthead: a left spaced-caps Barlow-Condensed * name + UPPERCASE role line, balanced by a right-aligned contact stack - * where each line ends with its PNG glyph icon (LinkedIn / GitHub / + * where each line ends with its recolorable SVG glyph icon (LinkedIn / GitHub / * location / phone / email), all under a thin full-width rule — the same * header as * {@link com.demcha.compose.document.templates.cv.v2.presets.TimelineMinimal}. @@ -72,8 +69,7 @@ public final class TimelineMinimalLetter { private static final double CONTACT_ICON_BASELINE_OFFSET = -1.35; private static final String CONTACT_ICON_ROOT = "/templates/cv/timeline-minimal/icons/"; - private static final Map CONTACT_ICON_CACHE = - new ConcurrentHashMap<>(); + private static final DocumentColor ICON_COLOR = DocumentColor.rgb(58, 58, 58); private TimelineMinimalLetter() { } @@ -170,9 +166,8 @@ private void addContact(SectionBuilder section, CvIdentity identity) { rich.style(item.text(), textStyle); rich.plain(" "); if (item.iconFile() != null) { - rich.image(contactIcon(item.iconFile()), - CONTACT_ICON_SIZE, - CONTACT_ICON_SIZE, + rich.shape(glyph(item.iconFile()).outline(CONTACT_ICON_SIZE), + ICON_COLOR, null, InlineImageAlignment.CENTER, CONTACT_ICON_BASELINE_OFFSET, item.linkOptions()); @@ -188,13 +183,13 @@ private List contactItems(CvIdentity identity) { return List.of(); } List items = new ArrayList<>(); - addContactItem(items, "LOC", "location.png", + addContactItem(items, "LOC", "location.svg", identity.contact().address(), null); - addContactItem(items, "TEL", "phone.png", + addContactItem(items, "TEL", "phone.svg", identity.contact().phone(), null); String email = identity.contact().email(); if (!email.isBlank()) { - addContactItem(items, "@", "email.png", email, + addContactItem(items, "@", "email.svg", email, new DocumentLinkOptions("mailto:" + email)); } for (CvLink link : identity.links()) { @@ -210,10 +205,8 @@ private List contactItems(CvIdentity identity) { return List.copyOf(items); } - private DocumentImageData contactIcon(String iconFile) { - return DocumentImageData.fromBytes( - CONTACT_ICON_CACHE.computeIfAbsent(iconFile, - TimelineMinimalLetter::readIconBytes)); + private SvgGlyph glyph(String iconFile) { + return SvgGlyph.fromResource(CONTACT_ICON_ROOT + iconFile); } private DocumentTextStyle nameStyle() { @@ -250,16 +243,16 @@ private static void addContactItem(List items, private static String pickIconFile(String label) { String normalized = SectionLookup.normalize(label); if (normalized.contains("linkedin")) { - return "linkedin.png"; + return "linkedin.svg"; } if (normalized.contains("github")) { - return "github.png"; + return "github.svg"; } if (normalized.contains("dribbble")) { - return "dribbble.png"; + return "dribbble.svg"; } if (normalized.contains("google")) { - return "google.png"; + return "google.svg"; } return null; } @@ -275,20 +268,6 @@ private static String pickFallbackIcon(String label) { return "@"; } - private static byte[] readIconBytes(String iconFile) { - try (InputStream input = TimelineMinimalLetter.class.getResourceAsStream( - CONTACT_ICON_ROOT + iconFile)) { - if (input == null) { - throw new IllegalStateException( - "Missing timeline minimal contact icon: " + iconFile); - } - return input.readAllBytes(); - } catch (IOException e) { - throw new UncheckedIOException( - "Failed to read timeline minimal contact icon: " + iconFile, e); - } - } - private record ContactItem(String fallbackIcon, String iconFile, String text, DocumentLinkOptions linkOptions) { } diff --git a/src/main/java/com/demcha/compose/document/templates/cv/presets/MonogramSidebar.java b/src/main/java/com/demcha/compose/document/templates/cv/presets/MonogramSidebar.java index e946da75..15cebbe5 100644 --- a/src/main/java/com/demcha/compose/document/templates/cv/presets/MonogramSidebar.java +++ b/src/main/java/com/demcha/compose/document/templates/cv/presets/MonogramSidebar.java @@ -6,7 +6,6 @@ import com.demcha.compose.document.dsl.ParagraphBuilder; import com.demcha.compose.document.dsl.RichText; import com.demcha.compose.document.dsl.SectionBuilder; -import com.demcha.compose.document.image.DocumentImageData; import com.demcha.compose.document.node.DocumentLinkOptions; import com.demcha.compose.document.node.InlineImageAlignment; import com.demcha.compose.document.node.InlineRun; @@ -21,6 +20,7 @@ import com.demcha.compose.document.style.DocumentTextDecoration; import com.demcha.compose.document.style.DocumentTextStyle; import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.cv.v2.widgets.SvgGlyph; import com.demcha.compose.document.templates.blocks.Block; import com.demcha.compose.document.templates.blocks.BulletListBlock; import com.demcha.compose.document.templates.blocks.IndentedBlock; @@ -35,15 +35,10 @@ import com.demcha.compose.document.theme.BusinessTheme; import com.demcha.compose.font.FontName; -import java.io.IOException; -import java.io.InputStream; -import java.io.UncheckedIOException; import java.util.ArrayList; import java.util.List; import java.util.Locale; -import java.util.Map; import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; /** * Templates v2 "Monogram Sidebar" CV preset. @@ -81,6 +76,8 @@ public final class MonogramSidebar { private static final DocumentColor MAIN_RULE = DocumentColor.rgb(72, 79, 84); private static final DocumentColor ACCENT = DocumentColor.rgb(158, 146, 104); private static final DocumentColor MONOGRAM_RING = DocumentColor.rgb(54, 62, 74); + /** Contact glyph fill — the muted gold accent, readable on the pale sidebar. */ + private static final DocumentColor ICON_COLOR = DocumentColor.rgb(158, 146, 104); private static final FontName HEADLINE_FONT = FontName.CRIMSON_TEXT; private static final FontName MONOGRAM_FONT = FontName.PT_SERIF; @@ -91,7 +88,6 @@ public final class MonogramSidebar { private static final double CONTACT_ICON_SIZE = 18; private static final String CONTACT_ICON_ROOT = "/templates/cv/monogram-sidebar/icons/"; - private static final Map CONTACT_ICON_CACHE = new ConcurrentHashMap<>(); private static final List EDUCATION_KEYS = List.of("education", "certifications"); private static final List SKILL_KEYS = List.of("skills", "technical skills", "expertise"); @@ -258,10 +254,10 @@ private void addContactBlock(SectionBuilder section, CvHeader header) { .textStyle(textStyle) .align(TextAlign.CENTER) .margin(DocumentInsets.top(4)) - .rich(rich -> rich.image( - contactIcon(contact.iconFile()), - CONTACT_ICON_SIZE, - CONTACT_ICON_SIZE, + .rich(rich -> rich.shape( + glyph(contact.iconFile()).outline(CONTACT_ICON_SIZE), + ICON_COLOR, + null, InlineImageAlignment.CENTER, 0.0, contact.linkOptions()))); @@ -660,13 +656,13 @@ private static List contactLines(CvHeader header) { return List.of(); } List lines = new ArrayList<>(); - addContactLine(lines, "phone.png", safe(header.phone()), null); + addContactLine(lines, "phone.svg", safe(header.phone()), null); String email = safe(header.email()); if (!email.isBlank()) { - addContactLine(lines, "email.png", email, + addContactLine(lines, "email.svg", email, new DocumentLinkOptions("mailto:" + email)); } - addContactLine(lines, "location.png", safe(header.address()), null); + addContactLine(lines, "location.svg", safe(header.address()), null); for (CvHeader.Link link : header.links()) { String label = safe(link.label()); if (label.isBlank()) { @@ -691,29 +687,14 @@ private static void addContactLine(List lines, String iconFile, private static String pickIconFile(String label) { String n = normalize(label); if (n.contains("github")) { - return "github.png"; + return "github.svg"; } - if (n.contains("linkedin")) { - return "linkedin.png"; - } - return "linkedin.png"; - } - - private static DocumentImageData contactIcon(String iconFile) { - return DocumentImageData.fromBytes( - CONTACT_ICON_CACHE.computeIfAbsent(CONTACT_ICON_ROOT + iconFile, - MonogramSidebar::readIconBytes)); + // LinkedIn and any other link → the LinkedIn glyph (V1 fallback). + return "linkedin.svg"; } - private static byte[] readIconBytes(String resourcePath) { - try (InputStream input = MonogramSidebar.class.getResourceAsStream(resourcePath)) { - if (input == null) { - throw new IllegalStateException("Missing monogram sidebar icon: " + resourcePath); - } - return input.readAllBytes(); - } catch (IOException e) { - throw new UncheckedIOException("Failed to read monogram sidebar icon: " + resourcePath, e); - } + private static SvgGlyph glyph(String iconFile) { + return SvgGlyph.fromResource(CONTACT_ICON_ROOT + iconFile); } private static String stripBasicMarkdown(String value) { diff --git a/src/main/java/com/demcha/compose/document/templates/cv/presets/SidebarPortrait.java b/src/main/java/com/demcha/compose/document/templates/cv/presets/SidebarPortrait.java index 44509a90..0ba2c034 100644 --- a/src/main/java/com/demcha/compose/document/templates/cv/presets/SidebarPortrait.java +++ b/src/main/java/com/demcha/compose/document/templates/cv/presets/SidebarPortrait.java @@ -3,7 +3,6 @@ import com.demcha.compose.document.api.DocumentSession; import com.demcha.compose.document.dsl.RichText; import com.demcha.compose.document.dsl.SectionBuilder; -import com.demcha.compose.document.image.DocumentImageData; import com.demcha.compose.document.node.DocumentLinkOptions; import com.demcha.compose.document.node.InlineImageAlignment; import com.demcha.compose.document.node.InlineRun; @@ -13,7 +12,9 @@ import com.demcha.compose.document.style.DocumentInsets; import com.demcha.compose.document.style.DocumentTextDecoration; import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.svg.SvgIcon; import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.cv.v2.widgets.SvgGlyph; import com.demcha.compose.document.templates.blocks.Block; import com.demcha.compose.document.templates.blocks.BulletListBlock; import com.demcha.compose.document.templates.blocks.IndentedBlock; @@ -31,6 +32,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Locale; @@ -75,6 +77,8 @@ public final class SidebarPortrait { private static final DocumentColor SIDEBAR_SOFT = SOFT; private static final DocumentColor ACCENT = DocumentColor.rgb(106, 106, 106); private static final DocumentColor RULE = DocumentColor.rgb(178, 178, 178); + /** Contact glyph fill — dark slate, readable on the pale-beige sidebar. */ + private static final DocumentColor ICON_COLOR = DocumentColor.rgb(58, 58, 58); private static final FontName DISPLAY_FONT = FontName.CRIMSON_TEXT; private static final FontName BODY_FONT = FontName.LATO; @@ -91,9 +95,9 @@ public final class SidebarPortrait { private static final String TEMPLATE_ASSET_ROOT = "/templates/cv/sidebar-portrait/"; private static final String CONTACT_ICON_ROOT = TEMPLATE_ASSET_ROOT + "icons/"; - private static final String PORTRAIT_FILE = "portrait.png"; + private static final String PORTRAIT_FILE = "portrait.svg"; - private static final Map ASSET_CACHE = new ConcurrentHashMap<>(); + private static final Map PORTRAIT_CACHE = new ConcurrentHashMap<>(); private static final List EDUCATION_KEYS = List.of("education", "certifications"); private static final List SKILL_KEYS = List.of("skills", "technical skills"); @@ -208,11 +212,15 @@ private void addSidebar(SectionBuilder section, CvSpec spec, double pageHeight) private void addPhotoBlock(SectionBuilder section) { double sideInset = Math.max(0.0, (SIDEBAR_INNER_WIDTH - PHOTO_DIAMETER) / 2.0); - section.addImage(image -> image + // Default avatar is the bundled portrait.svg, whose outermost + // layer is a full-frame filled circle, so it is already round at + // PHOTO_DIAMETER. Wrapped in a layer stack to keep the centred + // side insets + 17pt bottom margin (addSvgIcon has no margin + // overload). A user-supplied override is a follow-up. + section.addLayerStack(photo -> photo .name("SidebarPortraitPhoto") - .source(portraitImage()) - .size(PHOTO_DIAMETER, PHOTO_DIAMETER) - .margin(new DocumentInsets(0, sideInset, 17, sideInset))); + .margin(new DocumentInsets(0, sideInset, 17, sideInset)) + .layer(portraitIcon().node(PHOTO_DIAMETER))); } private void addContactBlock(SectionBuilder section, CvHeader header) { @@ -231,7 +239,8 @@ private void addContactBlock(SectionBuilder section, CvHeader header) { .link(contact.linkOptions()) .rich(rich -> { if (contact.iconFile() != null) { - rich.image(contactIcon(contact.iconFile()), 10.0, 10.0, + rich.shape(glyph(contact.iconFile()).outline(10.0), + ICON_COLOR, null, InlineImageAlignment.CENTER, 0.0, contact.linkOptions()); rich.style(" ", textStyle); } @@ -689,13 +698,13 @@ private static List contactLines(CvHeader header) { return List.of(); } List lines = new ArrayList<>(); - addContactLine(lines, "phone.png", safe(header.phone()), null); + addContactLine(lines, "phone.svg", safe(header.phone()), null); String email = safe(header.email()); if (!email.isBlank()) { - addContactLine(lines, "email.png", email, + addContactLine(lines, "email.svg", email, new DocumentLinkOptions("mailto:" + email)); } - addContactLine(lines, "location.png", safe(header.address()), null); + addContactLine(lines, "location.svg", safe(header.address()), null); for (CvHeader.Link link : header.links()) { String label = safe(link.label()); if (label.isBlank()) { @@ -719,39 +728,34 @@ private static void addContactLine(List lines, String iconFile, private static String pickIconFile(String label) { String n = normalize(label); - if (n.contains("linkedin")) { - return "linkedin.png"; - } if (n.contains("github")) { - return "github.png"; + return "github.svg"; } if (n.contains("dribbble")) { - return "dribbble.png"; + return "dribbble.svg"; } if (n.contains("google")) { - return "google.png"; + return "google.svg"; } - return "linkedin.png"; + // LinkedIn and any other link → the LinkedIn glyph (V1 fallback). + return "linkedin.svg"; } - private static DocumentImageData contactIcon(String iconFile) { - return DocumentImageData.fromBytes( - ASSET_CACHE.computeIfAbsent(CONTACT_ICON_ROOT + iconFile, - SidebarPortrait::readAssetBytes)); + private static SvgGlyph glyph(String iconFile) { + return SvgGlyph.fromResource(CONTACT_ICON_ROOT + iconFile); } - private static DocumentImageData portraitImage() { - return DocumentImageData.fromBytes( - ASSET_CACHE.computeIfAbsent(TEMPLATE_ASSET_ROOT + PORTRAIT_FILE, - SidebarPortrait::readAssetBytes)); + private static SvgIcon portraitIcon() { + return PORTRAIT_CACHE.computeIfAbsent(TEMPLATE_ASSET_ROOT + PORTRAIT_FILE, + SidebarPortrait::readSvgIcon); } - private static byte[] readAssetBytes(String resourcePath) { + private static SvgIcon readSvgIcon(String resourcePath) { try (InputStream input = SidebarPortrait.class.getResourceAsStream(resourcePath)) { if (input == null) { throw new IllegalStateException("Missing sidebar portrait asset: " + resourcePath); } - return input.readAllBytes(); + return SvgIcon.parse(new String(input.readAllBytes(), StandardCharsets.UTF_8)); } catch (IOException e) { throw new UncheckedIOException("Failed to read sidebar portrait asset: " + resourcePath, e); } diff --git a/src/main/java/com/demcha/compose/document/templates/cv/presets/TimelineMinimal.java b/src/main/java/com/demcha/compose/document/templates/cv/presets/TimelineMinimal.java index 6c6183b8..955cad36 100644 --- a/src/main/java/com/demcha/compose/document/templates/cv/presets/TimelineMinimal.java +++ b/src/main/java/com/demcha/compose/document/templates/cv/presets/TimelineMinimal.java @@ -4,7 +4,6 @@ import com.demcha.compose.document.dsl.LineBuilder; import com.demcha.compose.document.dsl.RowBuilder; import com.demcha.compose.document.dsl.SectionBuilder; -import com.demcha.compose.document.image.DocumentImageData; import com.demcha.compose.document.node.DocumentLinkOptions; import com.demcha.compose.document.node.InlineImageAlignment; import com.demcha.compose.document.node.TextAlign; @@ -14,6 +13,7 @@ import com.demcha.compose.document.style.DocumentTextDecoration; import com.demcha.compose.document.style.DocumentTextStyle; import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.cv.v2.widgets.SvgGlyph; import com.demcha.compose.document.templates.blocks.Block; import com.demcha.compose.document.templates.blocks.BulletListBlock; import com.demcha.compose.document.templates.blocks.IndentedBlock; @@ -27,15 +27,10 @@ import com.demcha.compose.document.theme.BusinessTheme; import com.demcha.compose.font.FontName; -import java.io.IOException; -import java.io.InputStream; -import java.io.UncheckedIOException; import java.util.ArrayList; import java.util.List; import java.util.Locale; -import java.util.Map; import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; /** * Templates v2 "Timeline Minimal" CV preset. @@ -70,6 +65,8 @@ public final class TimelineMinimal { private static final DocumentColor SOFT = DocumentColor.rgb(122, 122, 122); private static final DocumentColor RULE = DocumentColor.rgb(195, 195, 195); private static final DocumentColor DOT = DocumentColor.rgb(170, 170, 170); + /** Contact glyph fill — dark slate, readable on the white page. */ + private static final DocumentColor ICON_COLOR = DocumentColor.rgb(58, 58, 58); private static final double TIMELINE_DOT = 7.0; private static final double TIMELINE_LINE_BOX = 1.0; @@ -78,7 +75,6 @@ public final class TimelineMinimal { private static final double CONTACT_ICON_SIZE = 10.5; private static final double CONTACT_ICON_BASELINE_OFFSET = -1.35; private static final String CONTACT_ICON_ROOT = "/templates/cv/timeline-minimal/icons/"; - private static final Map CONTACT_ICON_CACHE = new ConcurrentHashMap<>(); private TimelineMinimal() { // utility class — not instantiable @@ -206,10 +202,10 @@ private void addContact(SectionBuilder section, CvHeader header) { rich.style(line.text(), textStyle); rich.plain(" "); if (line.iconFile() != null) { - rich.image( - contactIcon(line.iconFile()), - CONTACT_ICON_SIZE, - CONTACT_ICON_SIZE, + rich.shape( + glyph(line.iconFile()).outline(CONTACT_ICON_SIZE), + ICON_COLOR, + null, InlineImageAlignment.CENTER, CONTACT_ICON_BASELINE_OFFSET, line.linkOptions()); @@ -225,13 +221,13 @@ private List contactLines(CvHeader header) { return List.of(); } List lines = new ArrayList<>(); - addContactLine(lines, "LOC", "location.png", + addContactLine(lines, "LOC", "location.svg", safe(header.address()), null); - addContactLine(lines, "TEL", "phone.png", + addContactLine(lines, "TEL", "phone.svg", safe(header.phone()), null); String email = safe(header.email()); if (!email.isBlank()) { - addContactLine(lines, "@", "email.png", email, + addContactLine(lines, "@", "email.svg", email, new DocumentLinkOptions("mailto:" + email)); } for (CvHeader.Link link : header.links()) { @@ -258,9 +254,8 @@ private void addContactLine(List lines, } } - private DocumentImageData contactIcon(String iconFile) { - return DocumentImageData.fromBytes( - CONTACT_ICON_CACHE.computeIfAbsent(iconFile, TimelineMinimal::readIconBytes)); + private SvgGlyph glyph(String iconFile) { + return SvgGlyph.fromResource(CONTACT_ICON_ROOT + iconFile); } private void addSidebarModule(SectionBuilder sidebar, String title, @@ -360,31 +355,19 @@ private void addMainModule(SectionBuilder main, String title, // -- helpers --------------------------------------------------------- - private static byte[] readIconBytes(String iconFile) { - try (InputStream input = TimelineMinimal.class.getResourceAsStream( - CONTACT_ICON_ROOT + iconFile)) { - if (input == null) { - throw new IllegalStateException("Missing timeline minimal contact icon: " + iconFile); - } - return input.readAllBytes(); - } catch (IOException e) { - throw new UncheckedIOException("Failed to read timeline minimal contact icon: " + iconFile, e); - } - } - private static String pickIconFile(String label) { String n = normalize(label); if (n.contains("linkedin")) { - return "linkedin.png"; + return "linkedin.svg"; } if (n.contains("github")) { - return "github.png"; + return "github.svg"; } if (n.contains("dribbble")) { - return "dribbble.png"; + return "dribbble.svg"; } if (n.contains("google")) { - return "google.png"; + return "google.svg"; } return null; } diff --git a/src/main/java/com/demcha/compose/document/templates/cv/v2/presets/MintEditorial.java b/src/main/java/com/demcha/compose/document/templates/cv/v2/presets/MintEditorial.java index b04108b3..0c741f22 100644 --- a/src/main/java/com/demcha/compose/document/templates/cv/v2/presets/MintEditorial.java +++ b/src/main/java/com/demcha/compose/document/templates/cv/v2/presets/MintEditorial.java @@ -5,7 +5,6 @@ import com.demcha.compose.document.dsl.ParagraphBuilder; import com.demcha.compose.document.dsl.SectionBuilder; import com.demcha.compose.document.dsl.ShapeBuilder; -import com.demcha.compose.document.image.DocumentImageData; import com.demcha.compose.document.node.DocumentLinkOptions; import com.demcha.compose.document.node.DocumentNode; import com.demcha.compose.document.node.ParagraphNode; @@ -25,10 +24,13 @@ import com.demcha.compose.document.templates.cv.v2.widgets.IconTextRow; import com.demcha.compose.document.templates.cv.v2.widgets.SkillBar; import com.demcha.compose.document.templates.cv.v2.widgets.Subheadline; +import com.demcha.compose.document.templates.cv.v2.widgets.SvgGlyph; +import com.demcha.compose.document.svg.SvgIcon; import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -172,17 +174,17 @@ public final class MintEditorial { /** * Inline contact / social icon edge length (points). */ - private static final double CONTACT_ICON_SIZE = 9.0; + private static final double CONTACT_ICON_SIZE = 11.0; /** * Social icon edge length (points) — the filled badges read larger. */ - private static final double SOCIAL_ICON_SIZE = 12.0; + private static final double SOCIAL_ICON_SIZE = 14.0; /** * Expertise badge edge length (points). */ - private static final double BADGE_SIZE = 36.0; + private static final double BADGE_SIZE = 72.0; // Banded-masthead canvas geometry. These values reproduce the DEFAULT // (bandless) masthead flow positions exactly — name baseline, tagline @@ -222,7 +224,20 @@ public final class MintEditorial { static final double MASTHEAD_RULE_Y = 123.76; private static final String ICON_ROOT = "/templates/cv/mint-editorial/icons/"; - private static final Map ICON_CACHE = new ConcurrentHashMap<>(); + + /** + * Contact / social glyph fill — a deep teal-ink that reads clearly on the + * white editorial page and harmonises with the mint accent. Recolours the + * shared {@link SvgGlyph} silhouettes via {@code rich.shape(...)}. + */ + private static final DocumentColor ICON_COLOR = DocumentColor.rgb(47, 122, 106); + + /** + * Cached SVG source for the expertise badge — parsed once into an + * {@link SvgIcon} so the multi-stroke badge keeps its authored look. + */ + private static final String BADGE_SVG = ICON_ROOT + "expertise-badge.svg"; + private static final Map SVG_CACHE = new ConcurrentHashMap<>(); private static final List INTERESTS_KEYS = List.of("interests", "interest"); @@ -661,19 +676,22 @@ private void addContact(SectionBuilder section, CvIdentity identity) { DocumentTextStyle style = contactStyle(); String phone = identity.contact().phone(); if (!phone.isBlank()) { - IconTextRow.render(block, icon("phone.png"), CONTACT_ICON_SIZE, - phone, style, null, DocumentInsets.bottom(13)); + IconTextRow.render(block, glyph("phone.svg"), ICON_COLOR, + CONTACT_ICON_SIZE, phone, style, null, + DocumentInsets.bottom(13)); } String email = identity.contact().email(); if (!email.isBlank()) { - IconTextRow.render(block, icon("email.png"), CONTACT_ICON_SIZE, - email, style, new DocumentLinkOptions("mailto:" + email), + IconTextRow.render(block, glyph("email.svg"), ICON_COLOR, + CONTACT_ICON_SIZE, email, style, + new DocumentLinkOptions("mailto:" + email), DocumentInsets.bottom(13)); } String address = identity.contact().address(); if (!address.isBlank()) { - IconTextRow.render(block, icon("location.png"), CONTACT_ICON_SIZE, - address, style, null, DocumentInsets.bottom(13)); + IconTextRow.render(block, glyph("location.svg"), ICON_COLOR, + CONTACT_ICON_SIZE, address, style, null, + DocumentInsets.bottom(13)); } for (CvLink link : identity.links()) { if (link.label().isBlank()) { @@ -682,9 +700,9 @@ email, style, new DocumentLinkOptions("mailto:" + email), DocumentLinkOptions options = link.url().isBlank() ? null : new DocumentLinkOptions(link.url().trim()); - IconTextRow.render(block, icon(contactIconFile(link.label())), - CONTACT_ICON_SIZE, link.label(), style, options, - DocumentInsets.bottom(13)); + IconTextRow.render(block, glyph(contactIconFile(link.label())), + ICON_COLOR, CONTACT_ICON_SIZE, link.label(), style, + options, DocumentInsets.bottom(13)); } }); } @@ -759,11 +777,15 @@ private void addExpertise(SectionBuilder section, CvSection skills) { section.addSection("CvV2MintEditorialExpertise", block -> { block.spacing(0).padding(DocumentInsets.zero()); addBlockHeading(block, "Expertise"); - block.addImage(image -> image + // Badge is a stroked multi-path SVG (a checkmark in a ring), + // so it renders through SvgIcon.node — preserving its authored + // stroke — rather than being flattened to a recoloured glyph. + // Wrapped in a layer stack only to carry the 18pt bottom margin + // (addSvgIcon has no margin overload). + block.addLayerStack(badge -> badge .name("CvV2MintEditorialExpertiseBadge") - .source(icon("expertise-badge.png")) - .size(BADGE_SIZE, BADGE_SIZE) - .margin(DocumentInsets.bottom(18))); + .margin(DocumentInsets.bottom(18)) + .layer(badgeIcon().node(BADGE_SIZE))); for (String category : categories.stream().limit(EXPERTISE_LIMIT).toList()) { addLabel(block, category); } @@ -810,9 +832,9 @@ private void addSocial(SectionBuilder section, CvIdentity identity) { DocumentLinkOptions options = link.url().isBlank() ? null : new DocumentLinkOptions(link.url().trim()); - IconTextRow.render(block, icon(socialIconFile(link.label())), - SOCIAL_ICON_SIZE, link.label(), style, options, - DocumentInsets.bottom(11)); + IconTextRow.render(block, glyph(socialIconFile(link.label())), + ICON_COLOR, SOCIAL_ICON_SIZE, link.label(), style, + options, DocumentInsets.bottom(11)); } }); } @@ -1158,12 +1180,6 @@ private DocumentTableStyle cellStyle(DocumentTextStyle textStyle, .fillColor(DocumentColor.WHITE) .build(); } - - private DocumentImageData icon(String fileName) { - return DocumentImageData.fromBytes( - ICON_CACHE.computeIfAbsent(ICON_ROOT + fileName, - MintEditorial::readIconBytes)); - } } // -- Static helpers ---------------------------------------------------- @@ -1250,19 +1266,19 @@ private static String extractEmail(String line) { private static String contactIconFile(String label) { String normalized = SectionLookup.normalize(label); if (normalized.contains("linkedin")) { - return "linkedin.png"; + return "linkedin.svg"; } if (normalized.contains("twitter")) { - return "twitter.png"; + return "twitter.svg"; } if (normalized.contains("facebook")) { - return "facebook.png"; + return "facebook.svg"; } if (normalized.contains("pinterest")) { - return "pinterest.png"; + return "pinterest.svg"; } // GitHub, portfolio, personal site, etc. → the globe glyph. - return "website.png"; + return "website.svg"; } /** @@ -1271,29 +1287,44 @@ private static String contactIconFile(String label) { private static String socialIconFile(String label) { String normalized = SectionLookup.normalize(label); if (normalized.contains("twitter")) { - return "twitter.png"; + return "twitter.svg"; } if (normalized.contains("facebook")) { - return "facebook.png"; + return "facebook.svg"; } if (normalized.contains("pinterest")) { - return "pinterest.png"; + return "pinterest.svg"; } if (normalized.contains("linkedin")) { - return "linkedin.png"; + return "linkedin.svg"; } // GitHub, portfolio, personal site, etc. → the globe glyph. - return "website.png"; + return "website.svg"; + } + + /** + * Loads (and caches) a recolorable contact / social glyph by file name. + */ + private static SvgGlyph glyph(String fileName) { + return SvgGlyph.fromResource(ICON_ROOT + fileName); + } + + /** + * Loads (and caches) the expertise badge as a stroked {@link SvgIcon}. + */ + private static SvgIcon badgeIcon() { + return SVG_CACHE.computeIfAbsent(BADGE_SVG, MintEditorial::readSvgIcon); } - private static byte[] readIconBytes(String resourcePath) { + private static SvgIcon readSvgIcon(String resourcePath) { try (InputStream input = MintEditorial.class .getResourceAsStream(resourcePath)) { if (input == null) { throw new IllegalStateException( "Missing mint editorial icon: " + resourcePath); } - return input.readAllBytes(); + return SvgIcon.parse( + new String(input.readAllBytes(), StandardCharsets.UTF_8)); } catch (IOException e) { throw new UncheckedIOException( "Failed to read mint editorial icon: " + resourcePath, e); diff --git a/src/main/java/com/demcha/compose/document/templates/cv/v2/presets/MonogramSidebar.java b/src/main/java/com/demcha/compose/document/templates/cv/v2/presets/MonogramSidebar.java index bf3c6316..4bf3fcfc 100644 --- a/src/main/java/com/demcha/compose/document/templates/cv/v2/presets/MonogramSidebar.java +++ b/src/main/java/com/demcha/compose/document/templates/cv/v2/presets/MonogramSidebar.java @@ -6,20 +6,16 @@ import com.demcha.compose.document.dsl.LayerStackBuilder; import com.demcha.compose.document.dsl.ParagraphBuilder; import com.demcha.compose.document.dsl.SectionBuilder; -import com.demcha.compose.document.image.DocumentImageData; import com.demcha.compose.document.node.*; import com.demcha.compose.document.style.*; import com.demcha.compose.document.templates.api.DocumentTemplate; import com.demcha.compose.document.templates.cv.v2.components.*; import com.demcha.compose.document.templates.cv.v2.data.*; import com.demcha.compose.document.templates.cv.v2.theme.CvTheme; +import com.demcha.compose.document.templates.cv.v2.widgets.SvgGlyph; import com.demcha.compose.font.FontName; -import java.io.IOException; -import java.io.InputStream; -import java.io.UncheckedIOException; import java.util.*; -import java.util.concurrent.ConcurrentHashMap; /** * v2 port of the legacy "Monogram Sidebar" CV preset. @@ -64,6 +60,15 @@ public final class MonogramSidebar { private static final DocumentColor DEFAULT_ACCENT = DocumentColor.rgb(158, 146, 104); + /** + * Contact glyph fill — the muted gold accent, which reads clearly on the + * pale teal-grey sidebar fill and ties the centred icon stack to the + * subtitle / date accent. Recolours the shared {@link SvgGlyph} + * silhouettes via {@code rich.shape(...)}. + */ + private static final DocumentColor ICON_COLOR = + DocumentColor.rgb(158, 146, 104); + /** * V1 default dark monogram ring + initials colour. */ @@ -100,8 +105,6 @@ public final class MonogramSidebar { private static final String CONTACT_ICON_ROOT = "/templates/cv/monogram-sidebar/icons/"; - private static final Map CONTACT_ICON_CACHE = - new ConcurrentHashMap<>(); private static final List EDUCATION_KEYS = List.of("education", "certifications"); @@ -443,10 +446,10 @@ private void addContactBlock(SectionBuilder section, CvIdentity identity) { .textStyle(textStyle) .align(TextAlign.CENTER) .margin(DocumentInsets.top(4)) - .rich(rich -> rich.image( - contactIcon(item.iconFile()), - CONTACT_ICON_SIZE, - CONTACT_ICON_SIZE, + .rich(rich -> rich.shape( + glyph(item.iconFile()).outline(CONTACT_ICON_SIZE), + ICON_COLOR, + null, InlineImageAlignment.CENTER, 0.0, item.linkOptions()))); @@ -837,13 +840,13 @@ private static List contactItems(CvIdentity identity) { return List.of(); } List items = new ArrayList<>(); - addContactItem(items, "phone.png", identity.contact().phone(), null); + addContactItem(items, "phone.svg", identity.contact().phone(), null); String email = identity.contact().email(); if (!email.isBlank()) { - addContactItem(items, "email.png", email, + addContactItem(items, "email.svg", email, new DocumentLinkOptions("mailto:" + email)); } - addContactItem(items, "location.png", identity.contact().address(), + addContactItem(items, "location.svg", identity.contact().address(), null); for (CvLink link : identity.links()) { String label = link.label(); @@ -870,32 +873,14 @@ private static void addContactItem(List items, private static String pickIconFile(String label) { String normalized = SectionLookup.normalize(label); if (normalized.contains("github")) { - return "github.png"; - } - if (normalized.contains("linkedin")) { - return "linkedin.png"; + return "github.svg"; } - return "linkedin.png"; + // LinkedIn and any other link → the LinkedIn glyph (V1 fallback). + return "linkedin.svg"; } - private static DocumentImageData contactIcon(String iconFile) { - return DocumentImageData.fromBytes( - CONTACT_ICON_CACHE.computeIfAbsent(CONTACT_ICON_ROOT + iconFile, - MonogramSidebar::readIconBytes)); - } - - private static byte[] readIconBytes(String resourcePath) { - try (InputStream input = MonogramSidebar.class - .getResourceAsStream(resourcePath)) { - if (input == null) { - throw new IllegalStateException( - "Missing monogram sidebar icon: " + resourcePath); - } - return input.readAllBytes(); - } catch (IOException e) { - throw new UncheckedIOException( - "Failed to read monogram sidebar icon: " + resourcePath, e); - } + private static SvgGlyph glyph(String iconFile) { + return SvgGlyph.fromResource(CONTACT_ICON_ROOT + iconFile); } private static List skillTokens(SkillsSection skills) { diff --git a/src/main/java/com/demcha/compose/document/templates/cv/v2/presets/SidebarPortrait.java b/src/main/java/com/demcha/compose/document/templates/cv/v2/presets/SidebarPortrait.java index be4e6f5b..a92e9696 100644 --- a/src/main/java/com/demcha/compose/document/templates/cv/v2/presets/SidebarPortrait.java +++ b/src/main/java/com/demcha/compose/document/templates/cv/v2/presets/SidebarPortrait.java @@ -3,7 +3,6 @@ import com.demcha.compose.document.api.DocumentSession; import com.demcha.compose.document.api.PageBackgroundFill; import com.demcha.compose.document.dsl.SectionBuilder; -import com.demcha.compose.document.image.DocumentImageData; import com.demcha.compose.document.node.DocumentLinkOptions; import com.demcha.compose.document.node.InlineImageAlignment; import com.demcha.compose.document.node.TextAlign; @@ -11,6 +10,7 @@ import com.demcha.compose.document.style.DocumentInsets; import com.demcha.compose.document.style.DocumentTextDecoration; import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.svg.SvgIcon; import com.demcha.compose.document.templates.api.DocumentTemplate; import com.demcha.compose.document.templates.cv.v2.components.CvTextStyles; import com.demcha.compose.document.templates.cv.v2.components.MarkdownInline; @@ -18,10 +18,12 @@ import com.demcha.compose.document.templates.cv.v2.components.SectionLookup; import com.demcha.compose.document.templates.cv.v2.data.*; import com.demcha.compose.document.templates.cv.v2.theme.CvTheme; +import com.demcha.compose.document.templates.cv.v2.widgets.SvgGlyph; import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; import java.util.*; import java.util.concurrent.ConcurrentHashMap; @@ -72,6 +74,13 @@ public final class SidebarPortrait { private static final DocumentColor DEFAULT_ACCENT = DocumentColor.rgb(106, 106, 106); + /** + * Contact glyph fill — a dark slate that reads clearly on the pale-beige + * portrait sidebar fill. Recolours the shared {@link SvgGlyph} silhouettes + * via {@code rich.shape(...)}. + */ + private static final DocumentColor ICON_COLOR = DocumentColor.rgb(58, 58, 58); + /** * Inner content width of the sidebar column. Derived from the V1 * SidebarPortrait token set (sidebar outer width minus 13pt left + @@ -133,8 +142,8 @@ public final class SidebarPortrait { "/templates/cv/sidebar-portrait/"; private static final String CONTACT_ICON_ROOT = TEMPLATE_ASSET_ROOT + "icons/"; - private static final String PORTRAIT_FILE = "portrait.png"; - private static final Map ASSET_CACHE = + private static final String PORTRAIT_FILE = "portrait.svg"; + private static final Map PORTRAIT_CACHE = new ConcurrentHashMap<>(); private static final List EDUCATION_KEYS = @@ -405,11 +414,16 @@ private void addSidebar(SectionBuilder section, CvDocument doc, private void addPhotoBlock(SectionBuilder section) { double sideInset = Math.max(0.0, (SIDEBAR_INNER_WIDTH - PHOTO_DIAMETER) / 2.0); - section.addImage(image -> image + // Default avatar is the bundled portrait.svg, whose outermost + // layer is a full-frame filled circle, so the illustration is + // already round at PHOTO_DIAMETER — no extra circular clip is + // needed. Wrapped in a layer stack so the photo keeps its + // centred side insets + 17pt bottom margin (addSvgIcon has no + // margin overload). A user-supplied override is a follow-up. + section.addLayerStack(photo -> photo .name("CvV2SidebarPortraitPhoto") - .source(portraitImage()) - .size(PHOTO_DIAMETER, PHOTO_DIAMETER) - .margin(new DocumentInsets(0, sideInset, 17, sideInset))); + .margin(new DocumentInsets(0, sideInset, 17, sideInset)) + .layer(portraitIcon().node(PHOTO_DIAMETER))); } /** @@ -439,8 +453,8 @@ private void addContactBlock(SectionBuilder section, CvIdentity identity) { .link(item.linkOptions()) .rich(rich -> { if (item.iconFile() != null) { - rich.image(contactIcon(item.iconFile()), - 10.0, 10.0, + rich.shape(glyph(item.iconFile()).outline(10.0), + ICON_COLOR, null, InlineImageAlignment.CENTER, 0.0, item.linkOptions()); rich.style(" ", textStyle); @@ -874,13 +888,13 @@ private static List contactItems(CvIdentity identity) { return List.of(); } List items = new ArrayList<>(); - addContactItem(items, "phone.png", identity.contact().phone(), null); + addContactItem(items, "phone.svg", identity.contact().phone(), null); String email = identity.contact().email(); if (!email.isBlank()) { - addContactItem(items, "email.png", email, + addContactItem(items, "email.svg", email, new DocumentLinkOptions("mailto:" + email)); } - addContactItem(items, "location.png", identity.contact().address(), + addContactItem(items, "location.svg", identity.contact().address(), null); for (CvLink link : identity.links()) { String label = link.label(); @@ -906,41 +920,37 @@ private static void addContactItem(List items, private static String pickIconFile(String label) { String normalized = SectionLookup.normalize(label); - if (normalized.contains("linkedin")) { - return "linkedin.png"; - } if (normalized.contains("github")) { - return "github.png"; + return "github.svg"; } if (normalized.contains("dribbble")) { - return "dribbble.png"; + return "dribbble.svg"; } if (normalized.contains("google")) { - return "google.png"; + return "google.svg"; } - return "linkedin.png"; + // LinkedIn and any other link → the LinkedIn glyph (V1 fallback). + return "linkedin.svg"; } - private static DocumentImageData contactIcon(String iconFile) { - return DocumentImageData.fromBytes( - ASSET_CACHE.computeIfAbsent(CONTACT_ICON_ROOT + iconFile, - SidebarPortrait::readAssetBytes)); + private static SvgGlyph glyph(String iconFile) { + return SvgGlyph.fromResource(CONTACT_ICON_ROOT + iconFile); } - private static DocumentImageData portraitImage() { - return DocumentImageData.fromBytes( - ASSET_CACHE.computeIfAbsent(TEMPLATE_ASSET_ROOT + PORTRAIT_FILE, - SidebarPortrait::readAssetBytes)); + private static SvgIcon portraitIcon() { + return PORTRAIT_CACHE.computeIfAbsent(TEMPLATE_ASSET_ROOT + PORTRAIT_FILE, + SidebarPortrait::readSvgIcon); } - private static byte[] readAssetBytes(String resourcePath) { + private static SvgIcon readSvgIcon(String resourcePath) { try (InputStream input = SidebarPortrait.class .getResourceAsStream(resourcePath)) { if (input == null) { throw new IllegalStateException( "Missing sidebar portrait asset: " + resourcePath); } - return input.readAllBytes(); + return SvgIcon.parse( + new String(input.readAllBytes(), StandardCharsets.UTF_8)); } catch (IOException e) { throw new UncheckedIOException( "Failed to read sidebar portrait asset: " + resourcePath, diff --git a/src/main/java/com/demcha/compose/document/templates/cv/v2/presets/TimelineMinimal.java b/src/main/java/com/demcha/compose/document/templates/cv/v2/presets/TimelineMinimal.java index 64e40e7b..40a9e315 100644 --- a/src/main/java/com/demcha/compose/document/templates/cv/v2/presets/TimelineMinimal.java +++ b/src/main/java/com/demcha/compose/document/templates/cv/v2/presets/TimelineMinimal.java @@ -3,10 +3,10 @@ import com.demcha.compose.document.api.DocumentSession; import com.demcha.compose.document.dsl.RowBuilder; import com.demcha.compose.document.dsl.SectionBuilder; -import com.demcha.compose.document.image.DocumentImageData; import com.demcha.compose.document.node.DocumentLinkOptions; import com.demcha.compose.document.node.InlineImageAlignment; import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.DocumentColor; import com.demcha.compose.document.style.DocumentInsets; import com.demcha.compose.document.style.DocumentStroke; import com.demcha.compose.document.style.DocumentTextDecoration; @@ -17,13 +17,10 @@ import com.demcha.compose.document.templates.cv.v2.components.SectionLookup; import com.demcha.compose.document.templates.cv.v2.data.*; import com.demcha.compose.document.templates.cv.v2.theme.CvTheme; +import com.demcha.compose.document.templates.cv.v2.widgets.SvgGlyph; import com.demcha.compose.document.templates.widgets.TimelineAxisWidget; -import java.io.IOException; -import java.io.InputStream; -import java.io.UncheckedIOException; import java.util.*; -import java.util.concurrent.ConcurrentHashMap; /** * v2 port of the legacy "Timeline Minimal" CV preset. @@ -94,8 +91,13 @@ public final class TimelineMinimal { private static final double CONTACT_ICON_BASELINE_OFFSET = -1.35; private static final String CONTACT_ICON_ROOT = "/templates/cv/timeline-minimal/icons/"; - private static final Map CONTACT_ICON_CACHE = - new ConcurrentHashMap<>(); + + /** + * Contact glyph fill — a dark slate that reads clearly on the white page. + * Recolours the shared {@link SvgGlyph} silhouettes via + * {@code rich.shape(...)}. + */ + private static final DocumentColor ICON_COLOR = DocumentColor.rgb(58, 58, 58); private static final List SUMMARY_KEYS = List.of("summary", "professional summary", "profile"); @@ -256,9 +258,9 @@ private void addContact(SectionBuilder section, CvIdentity identity) { rich.style(item.text(), textStyle); rich.plain(" "); if (item.iconFile() != null) { - rich.image(contactIcon(item.iconFile()), - CONTACT_ICON_SIZE, - CONTACT_ICON_SIZE, + rich.shape(glyph(item.iconFile()) + .outline(CONTACT_ICON_SIZE), + ICON_COLOR, null, InlineImageAlignment.CENTER, CONTACT_ICON_BASELINE_OFFSET, item.linkOptions()); @@ -275,13 +277,13 @@ private List contactItems(CvIdentity identity) { return List.of(); } List items = new ArrayList<>(); - addContactItem(items, "LOC", "location.png", + addContactItem(items, "LOC", "location.svg", identity.contact().address(), null); - addContactItem(items, "TEL", "phone.png", + addContactItem(items, "TEL", "phone.svg", identity.contact().phone(), null); String email = identity.contact().email(); if (!email.isBlank()) { - addContactItem(items, "@", "email.png", email, + addContactItem(items, "@", "email.svg", email, new DocumentLinkOptions("mailto:" + email)); } for (CvLink link : identity.links()) { @@ -310,10 +312,8 @@ private static void addContactItem(List items, } } - private DocumentImageData contactIcon(String iconFile) { - return DocumentImageData.fromBytes( - CONTACT_ICON_CACHE.computeIfAbsent(iconFile, - TimelineMinimal::readIconBytes)); + private SvgGlyph glyph(String iconFile) { + return SvgGlyph.fromResource(CONTACT_ICON_ROOT + iconFile); } private void addSidebarModule(SectionBuilder sidebar, String title, @@ -557,34 +557,19 @@ private static void addLines(List lines, String value) { } } - private static byte[] readIconBytes(String iconFile) { - try (InputStream input = TimelineMinimal.class.getResourceAsStream( - CONTACT_ICON_ROOT + iconFile)) { - if (input == null) { - throw new IllegalStateException( - "Missing timeline minimal contact icon: " + iconFile); - } - return input.readAllBytes(); - } catch (IOException e) { - throw new UncheckedIOException( - "Failed to read timeline minimal contact icon: " + iconFile, - e); - } - } - private static String pickIconFile(String label) { String normalized = SectionLookup.normalize(label); if (normalized.contains("linkedin")) { - return "linkedin.png"; + return "linkedin.svg"; } if (normalized.contains("github")) { - return "github.png"; + return "github.svg"; } if (normalized.contains("dribbble")) { - return "dribbble.png"; + return "dribbble.svg"; } if (normalized.contains("google")) { - return "google.png"; + return "google.svg"; } return null; } diff --git a/src/main/java/com/demcha/compose/document/templates/cv/v2/widgets/IconTextRow.java b/src/main/java/com/demcha/compose/document/templates/cv/v2/widgets/IconTextRow.java index a7995432..b5f115e0 100644 --- a/src/main/java/com/demcha/compose/document/templates/cv/v2/widgets/IconTextRow.java +++ b/src/main/java/com/demcha/compose/document/templates/cv/v2/widgets/IconTextRow.java @@ -5,6 +5,7 @@ import com.demcha.compose.document.node.DocumentLinkOptions; import com.demcha.compose.document.node.InlineImageAlignment; import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.DocumentColor; import com.demcha.compose.document.style.DocumentInsets; import com.demcha.compose.document.style.DocumentTextStyle; @@ -17,16 +18,20 @@ *

What it renders

* *

One left-aligned paragraph holding two inline runs: a centred inline - * image of {@code iconSize}×{@code iconSize}, then three spaces and the + * glyph of {@code iconSize}×{@code iconSize}, then three spaces and the * label text. When a {@link DocumentLinkOptions} is supplied it is applied - * to both the image run and the text run, so the whole row + * to both the glyph run and the text run, so the whole row * (icon + label) is one clickable rectangle in the PDF — mirroring the flat * Mint Editorial blueprint's {@code iconLine()} contact / social rows.

* + *

The glyph is a recolorable {@link SvgGlyph} silhouette filled with the + * caller's {@code glyphColor} via {@code rich.shape(...)}, so the same bundled + * SVG renders in each template's own accent without per-template icon copies.

+ * *

When to use

* *

Reach for {@code IconTextRow} when a sidebar stacks contact details or - * social links as PNG-glyph rows (phone / email / location / website / + * social links as glyph rows (phone / email / location / website / * LinkedIn …) where each entire row should be clickable. It differs from the * shared {@code ContactLine} variants — those assume pipe-separated text or a * stacked link list with no per-row glyph — so this is the icon-driven row @@ -50,15 +55,63 @@ private IconTextRow() { } /** - * Renders an icon + text row. + * Renders a recolorable-glyph + text row. + * + * @param host host section the row paragraph is appended to + * @param glyph recolorable vector glyph drawn before the label; when + * {@code null} the row renders text only + * @param glyphColor fill colour for the glyph silhouette; when + * {@code null} the glyph run is skipped + * @param iconSize icon edge width in points (height follows the glyph's + * aspect ratio) + * @param text label text rendered after the glyph; a blank label + * still renders the glyph, so callers should skip empty + * rows upstream if that is unwanted + * @param style text style for the label + * @param link optional link wrapping the whole row (icon + label); + * {@code null} renders a non-clickable row + * @param margin paragraph margin (vertical rhythm between rows) + */ + public static void render(SectionBuilder host, SvgGlyph glyph, + DocumentColor glyphColor, double iconSize, + String text, DocumentTextStyle style, + DocumentLinkOptions link, DocumentInsets margin) { + Objects.requireNonNull(host, "host"); + Objects.requireNonNull(style, "style"); + String label = text == null ? "" : text; + DocumentInsets rowMargin = margin == null ? DocumentInsets.zero() : margin; + + host.addParagraph(paragraph -> { + paragraph.textStyle(style) + .align(TextAlign.LEFT) + .link(link) + .margin(rowMargin) + .rich(rich -> { + if (glyph != null && glyphColor != null) { + rich.shape(glyph.outline(iconSize), glyphColor, null, + InlineImageAlignment.CENTER, 0.0, link); + } + if (link != null) { + rich.link(GAP + label, link); + } else { + rich.style(GAP + label, style); + } + }); + }); + } + + /** + * Renders an icon + text row from a pre-decoded raster glyph. + * + *

Public API retained for callers that supply their own raster + * {@link DocumentImageData} icon. The bundled CV presets use the + * recolorable {@link SvgGlyph} overload above; prefer it for new code.

* * @param host host section the row paragraph is appended to * @param icon glyph image payload (already decoded / cached by the * caller); when {@code null} the row renders text only * @param iconSize icon edge length in points (width == height) - * @param text label text rendered after the icon; a blank label - * still renders the icon, so callers should skip empty - * rows upstream if that is unwanted + * @param text label text rendered after the icon * @param style text style for the label * @param link optional link wrapping the whole row (icon + label); * {@code null} renders a non-clickable row diff --git a/src/main/java/com/demcha/compose/document/templates/cv/v2/widgets/SvgGlyph.java b/src/main/java/com/demcha/compose/document/templates/cv/v2/widgets/SvgGlyph.java new file mode 100644 index 00000000..8ee51189 --- /dev/null +++ b/src/main/java/com/demcha/compose/document/templates/cv/v2/widgets/SvgGlyph.java @@ -0,0 +1,140 @@ +package com.demcha.compose.document.templates.cv.v2.widgets; + +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentPathSegment; +import com.demcha.compose.document.style.ShapeOutline; +import com.demcha.compose.document.svg.SvgIcon; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * A bundled, recolorable single-colour vector glyph for CV contact / social + * rows. + * + *

What it is

+ * + *

CV templates draw small monochrome glyphs (phone / email / location / + * LinkedIn …). Since v1.8.0 these ship as classpath SVGs instead of raster + * PNGs, which shrinks the published artifact dramatically. The engine renders + * the colours baked into an SVG file as-is (there is no render-time tint), so + * this helper flattens an icon's filled layers into one + * {@link ShapeOutline} silhouette whose colour is then chosen by the caller via + * {@code rich.shape(outline, colour)}. That is what lets the same glyph render + * in each template's own accent colour without per-template copies.

+ * + *

How it works

+ * + *

{@link #fromResource(String)} parses the SVG with {@link SvgIcon} (which + * normalizes every path into one shared unit frame), concatenates the segments + * of the layers that carry a fill, and caches the result. Stroke-only + * decorative sub-paths ({@code fill="none"}) are dropped; if an icon has no + * filled layer at all its full geometry is used as a fallback so it still + * renders. {@link #outline(double)} scales that silhouette to a target width, + * preserving the icon's aspect ratio. The outline fills under the non-zero + * winding rule, so authored holes (handset cut-outs, letter counters) stay + * open.

+ * + *

Reuse

+ * + *

Lives in {@code cv/v2/widgets} as the shared glyph primitive behind + * {@link IconTextRow} and the per-preset contact rows. Reuse it instead of + * loading icon bytes by hand.

+ */ +public final class SvgGlyph { + + private static final Map CACHE = new ConcurrentHashMap<>(); + + private final List segments; + private final double aspectRatio; + + private SvgGlyph(List segments, double aspectRatio) { + this.segments = segments; + this.aspectRatio = aspectRatio; + } + + /** + * Loads and caches the flattened glyph for a classpath SVG resource. + * + * @param resourcePath absolute classpath path, e.g. + * {@code "/templates/cv/timeline-minimal/icons/email.svg"} + * @return the cached, recolorable glyph + * @throws IllegalStateException if the resource is missing + * @throws UncheckedIOException if the resource cannot be read + */ + public static SvgGlyph fromResource(String resourcePath) { + return CACHE.computeIfAbsent(resourcePath, SvgGlyph::load); + } + + private static SvgGlyph load(String resourcePath) { + try (InputStream input = SvgGlyph.class.getResourceAsStream(resourcePath)) { + if (input == null) { + throw new IllegalStateException("Missing CV glyph resource: " + resourcePath); + } + SvgIcon icon = SvgIcon.parse(new String(input.readAllBytes(), StandardCharsets.UTF_8)); + List filled = new ArrayList<>(); + for (SvgIcon.Layer layer : icon.layers()) { + if (isInkFill(layer.fill()) || layer.fillPaint() != null) { + filled.addAll(layer.geometry().segments()); + } + } + if (filled.isEmpty()) { + // Stroke-only / unfilled icon: keep every path so the glyph + // still renders as a filled silhouette rather than vanishing. + for (SvgIcon.Layer layer : icon.layers()) { + filled.addAll(layer.geometry().segments()); + } + } + if (filled.isEmpty()) { + throw new IllegalStateException("CV glyph has no drawable geometry: " + resourcePath); + } + return new SvgGlyph(List.copyOf(filled), icon.aspectRatio()); + } catch (IOException e) { + throw new UncheckedIOException("Failed to read CV glyph: " + resourcePath, e); + } + } + + /** + * Reports whether a fill is "ink" (part of the glyph) rather than a + * near-white background. Many svgrepo icons wrap the figure in a full-box + * {@code } backing plate; when every layer is recolored + * to one ink colour, that white paper would flood the whole box and swallow + * the figure (it reads as an inverted, solid tile). Treating near-white + * fills as background keeps only the real silhouette. + */ + private static boolean isInkFill(DocumentColor fill) { + if (fill == null) { + return false; + } + java.awt.Color awt = fill.color(); + return awt.getRed() < 245 || awt.getGreen() < 245 || awt.getBlue() < 245; + } + + /** + * Returns the glyph as a path outline at {@code width} points; the height + * follows the icon's aspect ratio. Fill it with any colour via + * {@code rich.shape(glyph.outline(w), colour)}. + * + * @param width target width in points; must be positive + * @return a path {@link ShapeOutline} of this glyph + */ + public ShapeOutline outline(double width) { + double height = width / aspectRatio; + return ShapeOutline.path(width, height, segments); + } + + /** + * Returns the glyph's width-to-height ratio for proportional sizing. + * + * @return aspect ratio (width / height) + */ + public double aspectRatio() { + return aspectRatio; + } +} diff --git a/src/main/resources/templates/cv/mint-editorial/icons/email.png b/src/main/resources/templates/cv/mint-editorial/icons/email.png deleted file mode 100644 index c3369c19..00000000 Binary files a/src/main/resources/templates/cv/mint-editorial/icons/email.png and /dev/null differ diff --git a/src/main/resources/templates/cv/mint-editorial/icons/email.svg b/src/main/resources/templates/cv/mint-editorial/icons/email.svg new file mode 100644 index 00000000..4edc99f4 --- /dev/null +++ b/src/main/resources/templates/cv/mint-editorial/icons/email.svg @@ -0,0 +1,24 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/cv/mint-editorial/icons/expertise-badge.png b/src/main/resources/templates/cv/mint-editorial/icons/expertise-badge.png deleted file mode 100644 index cdba6024..00000000 Binary files a/src/main/resources/templates/cv/mint-editorial/icons/expertise-badge.png and /dev/null differ diff --git a/src/main/resources/templates/cv/mint-editorial/icons/expertise-badge.svg b/src/main/resources/templates/cv/mint-editorial/icons/expertise-badge.svg new file mode 100644 index 00000000..6624c939 --- /dev/null +++ b/src/main/resources/templates/cv/mint-editorial/icons/expertise-badge.svg @@ -0,0 +1,2 @@ + + OK \ No newline at end of file diff --git a/src/main/resources/templates/cv/mint-editorial/icons/facebook.png b/src/main/resources/templates/cv/mint-editorial/icons/facebook.png deleted file mode 100644 index 75e0e24d..00000000 Binary files a/src/main/resources/templates/cv/mint-editorial/icons/facebook.png and /dev/null differ diff --git a/src/main/resources/templates/cv/mint-editorial/icons/facebook.svg b/src/main/resources/templates/cv/mint-editorial/icons/facebook.svg new file mode 100644 index 00000000..f0332cc6 --- /dev/null +++ b/src/main/resources/templates/cv/mint-editorial/icons/facebook.svg @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/cv/mint-editorial/icons/linkedin.png b/src/main/resources/templates/cv/mint-editorial/icons/linkedin.png deleted file mode 100644 index 5990eeab..00000000 Binary files a/src/main/resources/templates/cv/mint-editorial/icons/linkedin.png and /dev/null differ diff --git a/src/main/resources/templates/cv/mint-editorial/icons/linkedin.svg b/src/main/resources/templates/cv/mint-editorial/icons/linkedin.svg new file mode 100644 index 00000000..231539e6 --- /dev/null +++ b/src/main/resources/templates/cv/mint-editorial/icons/linkedin.svg @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/cv/mint-editorial/icons/location.png b/src/main/resources/templates/cv/mint-editorial/icons/location.png deleted file mode 100644 index 03454608..00000000 Binary files a/src/main/resources/templates/cv/mint-editorial/icons/location.png and /dev/null differ diff --git a/src/main/resources/templates/cv/mint-editorial/icons/location.svg b/src/main/resources/templates/cv/mint-editorial/icons/location.svg new file mode 100644 index 00000000..3a9314df --- /dev/null +++ b/src/main/resources/templates/cv/mint-editorial/icons/location.svg @@ -0,0 +1,7 @@ + + + + + diff --git a/src/main/resources/templates/cv/mint-editorial/icons/phone.png b/src/main/resources/templates/cv/mint-editorial/icons/phone.png deleted file mode 100644 index eee1b2d5..00000000 Binary files a/src/main/resources/templates/cv/mint-editorial/icons/phone.png and /dev/null differ diff --git a/src/main/resources/templates/cv/mint-editorial/icons/phone.svg b/src/main/resources/templates/cv/mint-editorial/icons/phone.svg new file mode 100644 index 00000000..64f07afb --- /dev/null +++ b/src/main/resources/templates/cv/mint-editorial/icons/phone.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/cv/mint-editorial/icons/pinterest.png b/src/main/resources/templates/cv/mint-editorial/icons/pinterest.png deleted file mode 100644 index 91326373..00000000 Binary files a/src/main/resources/templates/cv/mint-editorial/icons/pinterest.png and /dev/null differ diff --git a/src/main/resources/templates/cv/mint-editorial/icons/pinterest.svg b/src/main/resources/templates/cv/mint-editorial/icons/pinterest.svg new file mode 100644 index 00000000..44e85870 --- /dev/null +++ b/src/main/resources/templates/cv/mint-editorial/icons/pinterest.svg @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/cv/mint-editorial/icons/twitter.png b/src/main/resources/templates/cv/mint-editorial/icons/twitter.png deleted file mode 100644 index c43d9de3..00000000 Binary files a/src/main/resources/templates/cv/mint-editorial/icons/twitter.png and /dev/null differ diff --git a/src/main/resources/templates/cv/mint-editorial/icons/twitter.svg b/src/main/resources/templates/cv/mint-editorial/icons/twitter.svg new file mode 100644 index 00000000..c42c3908 --- /dev/null +++ b/src/main/resources/templates/cv/mint-editorial/icons/twitter.svg @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/cv/mint-editorial/icons/website.png b/src/main/resources/templates/cv/mint-editorial/icons/website.png deleted file mode 100644 index 3e29e34a..00000000 Binary files a/src/main/resources/templates/cv/mint-editorial/icons/website.png and /dev/null differ diff --git a/src/main/resources/templates/cv/mint-editorial/icons/website.svg b/src/main/resources/templates/cv/mint-editorial/icons/website.svg new file mode 100644 index 00000000..552c9b82 --- /dev/null +++ b/src/main/resources/templates/cv/mint-editorial/icons/website.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/cv/monogram-sidebar/icons/email.png b/src/main/resources/templates/cv/monogram-sidebar/icons/email.png deleted file mode 100644 index c5f270a5..00000000 Binary files a/src/main/resources/templates/cv/monogram-sidebar/icons/email.png and /dev/null differ diff --git a/src/main/resources/templates/cv/monogram-sidebar/icons/email.svg b/src/main/resources/templates/cv/monogram-sidebar/icons/email.svg new file mode 100644 index 00000000..4edc99f4 --- /dev/null +++ b/src/main/resources/templates/cv/monogram-sidebar/icons/email.svg @@ -0,0 +1,24 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/cv/monogram-sidebar/icons/github.png b/src/main/resources/templates/cv/monogram-sidebar/icons/github.png deleted file mode 100644 index db77f96f..00000000 Binary files a/src/main/resources/templates/cv/monogram-sidebar/icons/github.png and /dev/null differ diff --git a/src/main/resources/templates/cv/monogram-sidebar/icons/github.svg b/src/main/resources/templates/cv/monogram-sidebar/icons/github.svg new file mode 100644 index 00000000..84e4d2f9 --- /dev/null +++ b/src/main/resources/templates/cv/monogram-sidebar/icons/github.svg @@ -0,0 +1,19 @@ + + + + + github [#142] + Created with Sketch. + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/cv/monogram-sidebar/icons/linkedin.png b/src/main/resources/templates/cv/monogram-sidebar/icons/linkedin.png deleted file mode 100644 index 40b9aff2..00000000 Binary files a/src/main/resources/templates/cv/monogram-sidebar/icons/linkedin.png and /dev/null differ diff --git a/src/main/resources/templates/cv/monogram-sidebar/icons/linkedin.svg b/src/main/resources/templates/cv/monogram-sidebar/icons/linkedin.svg new file mode 100644 index 00000000..74a5f440 --- /dev/null +++ b/src/main/resources/templates/cv/monogram-sidebar/icons/linkedin.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/main/resources/templates/cv/monogram-sidebar/icons/location.png b/src/main/resources/templates/cv/monogram-sidebar/icons/location.png deleted file mode 100644 index 4d11dd9a..00000000 Binary files a/src/main/resources/templates/cv/monogram-sidebar/icons/location.png and /dev/null differ diff --git a/src/main/resources/templates/cv/monogram-sidebar/icons/location.svg b/src/main/resources/templates/cv/monogram-sidebar/icons/location.svg new file mode 100644 index 00000000..db5509a1 --- /dev/null +++ b/src/main/resources/templates/cv/monogram-sidebar/icons/location.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/cv/monogram-sidebar/icons/phone.png b/src/main/resources/templates/cv/monogram-sidebar/icons/phone.png deleted file mode 100644 index d1c7ef1f..00000000 Binary files a/src/main/resources/templates/cv/monogram-sidebar/icons/phone.png and /dev/null differ diff --git a/src/main/resources/templates/cv/monogram-sidebar/icons/phone.svg b/src/main/resources/templates/cv/monogram-sidebar/icons/phone.svg new file mode 100644 index 00000000..64f07afb --- /dev/null +++ b/src/main/resources/templates/cv/monogram-sidebar/icons/phone.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/cv/sidebar-portrait/icons/dribbble.png b/src/main/resources/templates/cv/sidebar-portrait/icons/dribbble.png deleted file mode 100644 index f9dc00fa..00000000 Binary files a/src/main/resources/templates/cv/sidebar-portrait/icons/dribbble.png and /dev/null differ diff --git a/src/main/resources/templates/cv/sidebar-portrait/icons/dribbble.svg b/src/main/resources/templates/cv/sidebar-portrait/icons/dribbble.svg new file mode 100644 index 00000000..18c9d8f0 --- /dev/null +++ b/src/main/resources/templates/cv/sidebar-portrait/icons/dribbble.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/cv/sidebar-portrait/icons/email.png b/src/main/resources/templates/cv/sidebar-portrait/icons/email.png deleted file mode 100644 index e66d8840..00000000 Binary files a/src/main/resources/templates/cv/sidebar-portrait/icons/email.png and /dev/null differ diff --git a/src/main/resources/templates/cv/sidebar-portrait/icons/email.svg b/src/main/resources/templates/cv/sidebar-portrait/icons/email.svg new file mode 100644 index 00000000..4edc99f4 --- /dev/null +++ b/src/main/resources/templates/cv/sidebar-portrait/icons/email.svg @@ -0,0 +1,24 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/cv/sidebar-portrait/icons/github.png b/src/main/resources/templates/cv/sidebar-portrait/icons/github.png deleted file mode 100644 index ddc1a210..00000000 Binary files a/src/main/resources/templates/cv/sidebar-portrait/icons/github.png and /dev/null differ diff --git a/src/main/resources/templates/cv/sidebar-portrait/icons/github.svg b/src/main/resources/templates/cv/sidebar-portrait/icons/github.svg new file mode 100644 index 00000000..84e4d2f9 --- /dev/null +++ b/src/main/resources/templates/cv/sidebar-portrait/icons/github.svg @@ -0,0 +1,19 @@ + + + + + github [#142] + Created with Sketch. + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/cv/sidebar-portrait/icons/google.png b/src/main/resources/templates/cv/sidebar-portrait/icons/google.png deleted file mode 100644 index 6863f072..00000000 Binary files a/src/main/resources/templates/cv/sidebar-portrait/icons/google.png and /dev/null differ diff --git a/src/main/resources/templates/cv/sidebar-portrait/icons/google.svg b/src/main/resources/templates/cv/sidebar-portrait/icons/google.svg new file mode 100644 index 00000000..2c7b79b9 --- /dev/null +++ b/src/main/resources/templates/cv/sidebar-portrait/icons/google.svg @@ -0,0 +1,19 @@ + + + + + google [#178] + Created with Sketch. + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/cv/sidebar-portrait/icons/linkedin.png b/src/main/resources/templates/cv/sidebar-portrait/icons/linkedin.png deleted file mode 100644 index 96da95dd..00000000 Binary files a/src/main/resources/templates/cv/sidebar-portrait/icons/linkedin.png and /dev/null differ diff --git a/src/main/resources/templates/cv/sidebar-portrait/icons/linkedin.svg b/src/main/resources/templates/cv/sidebar-portrait/icons/linkedin.svg new file mode 100644 index 00000000..74a5f440 --- /dev/null +++ b/src/main/resources/templates/cv/sidebar-portrait/icons/linkedin.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/main/resources/templates/cv/sidebar-portrait/icons/location.png b/src/main/resources/templates/cv/sidebar-portrait/icons/location.png deleted file mode 100644 index 99388ace..00000000 Binary files a/src/main/resources/templates/cv/sidebar-portrait/icons/location.png and /dev/null differ diff --git a/src/main/resources/templates/cv/sidebar-portrait/icons/location.svg b/src/main/resources/templates/cv/sidebar-portrait/icons/location.svg new file mode 100644 index 00000000..3a9314df --- /dev/null +++ b/src/main/resources/templates/cv/sidebar-portrait/icons/location.svg @@ -0,0 +1,7 @@ + + + + + diff --git a/src/main/resources/templates/cv/sidebar-portrait/icons/phone.png b/src/main/resources/templates/cv/sidebar-portrait/icons/phone.png deleted file mode 100644 index 83ba8ae1..00000000 Binary files a/src/main/resources/templates/cv/sidebar-portrait/icons/phone.png and /dev/null differ diff --git a/src/main/resources/templates/cv/sidebar-portrait/icons/phone.svg b/src/main/resources/templates/cv/sidebar-portrait/icons/phone.svg new file mode 100644 index 00000000..7c812852 --- /dev/null +++ b/src/main/resources/templates/cv/sidebar-portrait/icons/phone.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/cv/sidebar-portrait/portrait.png b/src/main/resources/templates/cv/sidebar-portrait/portrait.png deleted file mode 100644 index 1a06da59..00000000 Binary files a/src/main/resources/templates/cv/sidebar-portrait/portrait.png and /dev/null differ diff --git a/src/main/resources/templates/cv/sidebar-portrait/portrait.svg b/src/main/resources/templates/cv/sidebar-portrait/portrait.svg new file mode 100644 index 00000000..1284dd68 --- /dev/null +++ b/src/main/resources/templates/cv/sidebar-portrait/portrait.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/main/resources/templates/cv/timeline-minimal/icons/dribbble.png b/src/main/resources/templates/cv/timeline-minimal/icons/dribbble.png deleted file mode 100644 index cb397616..00000000 Binary files a/src/main/resources/templates/cv/timeline-minimal/icons/dribbble.png and /dev/null differ diff --git a/src/main/resources/templates/cv/timeline-minimal/icons/dribbble.svg b/src/main/resources/templates/cv/timeline-minimal/icons/dribbble.svg new file mode 100644 index 00000000..18c9d8f0 --- /dev/null +++ b/src/main/resources/templates/cv/timeline-minimal/icons/dribbble.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/cv/timeline-minimal/icons/email.png b/src/main/resources/templates/cv/timeline-minimal/icons/email.png deleted file mode 100644 index dd3f0263..00000000 Binary files a/src/main/resources/templates/cv/timeline-minimal/icons/email.png and /dev/null differ diff --git a/src/main/resources/templates/cv/timeline-minimal/icons/email.svg b/src/main/resources/templates/cv/timeline-minimal/icons/email.svg new file mode 100644 index 00000000..4edc99f4 --- /dev/null +++ b/src/main/resources/templates/cv/timeline-minimal/icons/email.svg @@ -0,0 +1,24 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/cv/timeline-minimal/icons/github.png b/src/main/resources/templates/cv/timeline-minimal/icons/github.png deleted file mode 100644 index a4ec311c..00000000 Binary files a/src/main/resources/templates/cv/timeline-minimal/icons/github.png and /dev/null differ diff --git a/src/main/resources/templates/cv/timeline-minimal/icons/github.svg b/src/main/resources/templates/cv/timeline-minimal/icons/github.svg new file mode 100644 index 00000000..84e4d2f9 --- /dev/null +++ b/src/main/resources/templates/cv/timeline-minimal/icons/github.svg @@ -0,0 +1,19 @@ + + + + + github [#142] + Created with Sketch. + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/cv/timeline-minimal/icons/google.png b/src/main/resources/templates/cv/timeline-minimal/icons/google.png deleted file mode 100644 index f0cf8374..00000000 Binary files a/src/main/resources/templates/cv/timeline-minimal/icons/google.png and /dev/null differ diff --git a/src/main/resources/templates/cv/timeline-minimal/icons/google.svg b/src/main/resources/templates/cv/timeline-minimal/icons/google.svg new file mode 100644 index 00000000..2c7b79b9 --- /dev/null +++ b/src/main/resources/templates/cv/timeline-minimal/icons/google.svg @@ -0,0 +1,19 @@ + + + + + google [#178] + Created with Sketch. + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/cv/timeline-minimal/icons/linkedin.png b/src/main/resources/templates/cv/timeline-minimal/icons/linkedin.png deleted file mode 100644 index 947cfd67..00000000 Binary files a/src/main/resources/templates/cv/timeline-minimal/icons/linkedin.png and /dev/null differ diff --git a/src/main/resources/templates/cv/timeline-minimal/icons/linkedin.svg b/src/main/resources/templates/cv/timeline-minimal/icons/linkedin.svg new file mode 100644 index 00000000..74a5f440 --- /dev/null +++ b/src/main/resources/templates/cv/timeline-minimal/icons/linkedin.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/main/resources/templates/cv/timeline-minimal/icons/location.png b/src/main/resources/templates/cv/timeline-minimal/icons/location.png deleted file mode 100644 index f3c800f4..00000000 Binary files a/src/main/resources/templates/cv/timeline-minimal/icons/location.png and /dev/null differ diff --git a/src/main/resources/templates/cv/timeline-minimal/icons/location.svg b/src/main/resources/templates/cv/timeline-minimal/icons/location.svg new file mode 100644 index 00000000..3a9314df --- /dev/null +++ b/src/main/resources/templates/cv/timeline-minimal/icons/location.svg @@ -0,0 +1,7 @@ + + + + + diff --git a/src/main/resources/templates/cv/timeline-minimal/icons/phone.png b/src/main/resources/templates/cv/timeline-minimal/icons/phone.png deleted file mode 100644 index 470b0e72..00000000 Binary files a/src/main/resources/templates/cv/timeline-minimal/icons/phone.png and /dev/null differ diff --git a/src/main/resources/templates/cv/timeline-minimal/icons/phone.svg b/src/main/resources/templates/cv/timeline-minimal/icons/phone.svg new file mode 100644 index 00000000..7c812852 --- /dev/null +++ b/src/main/resources/templates/cv/timeline-minimal/icons/phone.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/test/java/com/demcha/compose/document/templates/cv/v2/widgets/WidgetSmokeTest.java b/src/test/java/com/demcha/compose/document/templates/cv/v2/widgets/WidgetSmokeTest.java index d423d21e..61bda3ef 100644 --- a/src/test/java/com/demcha/compose/document/templates/cv/v2/widgets/WidgetSmokeTest.java +++ b/src/test/java/com/demcha/compose/document/templates/cv/v2/widgets/WidgetSmokeTest.java @@ -2,7 +2,6 @@ import com.demcha.compose.GraphCompose; import com.demcha.compose.document.api.DocumentSession; -import com.demcha.compose.document.image.DocumentImageData; import com.demcha.compose.document.node.TextAlign; import com.demcha.compose.document.style.DocumentColor; import com.demcha.compose.document.style.DocumentInsets; @@ -93,14 +92,15 @@ void skillBar_renders_with_and_without_level() throws Exception { @Test void iconTextRow_renders_with_and_without_link() throws Exception { CvTheme theme = CvTheme.mintEditorial(); - DocumentImageData icon = DocumentImageData.fromBytes(readMintIcon("phone.png")); + SvgGlyph icon = SvgGlyph.fromResource("/templates/cv/mint-editorial/icons/phone.svg"); + DocumentColor iconColor = DocumentColor.rgb(47, 122, 106); // Linked row (whole row clickable) and plain row. - renderWithSection(section -> IconTextRow.render(section, icon, 9.0, + renderWithSection(section -> IconTextRow.render(section, icon, iconColor, 9.0, "hello@example.com", theme.bodyStyle(), new com.demcha.compose.document.node.DocumentLinkOptions( "mailto:hello@example.com"), DocumentInsets.bottom(12))); - renderWithSection(section -> IconTextRow.render(section, icon, 9.0, + renderWithSection(section -> IconTextRow.render(section, icon, iconColor, 9.0, "London, UK", theme.bodyStyle(), null, DocumentInsets.bottom(12))); } @@ -214,14 +214,6 @@ private static void renderWithFlow(FlowAction action) throws Exception { } } - private static byte[] readMintIcon(String fileName) throws Exception { - try (var input = WidgetSmokeTest.class.getResourceAsStream( - "/templates/cv/mint-editorial/icons/" + fileName)) { - assertThat(input).as("mint editorial icon %s", fileName).isNotNull(); - return input.readAllBytes(); - } - } - private static CvName name() { return CvName.of("Jane", "Doe"); } diff --git a/src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_sidebar_portrait.json b/src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_sidebar_portrait.json index 31eff814..83753c24 100644 --- a/src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_sidebar_portrait.json +++ b/src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_sidebar_portrait.json @@ -22,15 +22,15 @@ "depth" : 1, "layer" : 1, "computedX" : 15.0, - "computedY" : 163.186, + "computedY" : 154.68, "placementX" : 15.0, - "placementY" : 163.186, + "placementY" : 154.68, "placementWidth" : 570.276, - "placementHeight" : 663.704, + "placementHeight" : 672.21, "startPage" : 0, "endPage" : 0, "contentWidth" : 570.276, - "contentHeight" : 663.704, + "contentHeight" : 672.21, "margin" : { "top" : 0.0, "right" : 0.0, @@ -52,15 +52,15 @@ "depth" : 2, "layer" : 2, "computedX" : 15.0, - "computedY" : 163.186, + "computedY" : 154.68, "placementX" : 15.0, - "placementY" : 163.186, + "placementY" : 154.68, "placementWidth" : 570.276, - "placementHeight" : 663.704, + "placementHeight" : 672.21, "startPage" : 0, "endPage" : 0, "contentWidth" : 570.276, - "contentHeight" : 663.704, + "contentHeight" : 672.21, "margin" : { "top" : 0.0, "right" : 0.0, @@ -82,15 +82,15 @@ "depth" : 3, "layer" : 3, "computedX" : 15.0, - "computedY" : 163.186, + "computedY" : 154.68, "placementX" : 15.0, - "placementY" : 163.186, + "placementY" : 154.68, "placementWidth" : 193.894, - "placementHeight" : 663.704, + "placementHeight" : 672.21, "startPage" : 0, "endPage" : 0, "contentWidth" : 193.894, - "contentHeight" : 663.704, + "contentHeight" : 672.21, "margin" : { "top" : 0.0, "right" : 0.0, @@ -106,21 +106,21 @@ }, { "path" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]", "entityName" : "SidebarPortraitPhoto", - "entityKind" : "ImageNode", + "entityKind" : "LayerStackNode", "parentPath" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]", "childIndex" : 0, "depth" : 4, "layer" : 4, "computedX" : 70.2, - "computedY" : 683.396, + "computedY" : 674.89, "placementX" : 70.2, - "placementY" : 683.396, + "placementY" : 674.89, "placementWidth" : 89.494, - "placementHeight" : 89.494, + "placementHeight" : 98.0, "startPage" : 0, "endPage" : 0, "contentWidth" : 89.494, - "contentHeight" : 89.494, + "contentHeight" : 98.0, "margin" : { "top" : 0.0, "right" : 29.2, @@ -133,6 +133,1146 @@ "bottom" : 0.0, "left" : 0.0 } + }, { + "path" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 70.2, + "computedY" : 674.89, + "placementX" : 70.2, + "placementY" : 674.89, + "placementWidth" : 89.494, + "placementHeight" : 98.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 89.494, + "contentHeight" : 98.0, + "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" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 70.2, + "computedY" : 674.89, + "placementX" : 70.2, + "placementY" : 674.89, + "placementWidth" : 98.0, + "placementHeight" : 98.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 98.0, + "contentHeight" : 98.0, + "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" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]/SvgLayer1[1]", + "entityName" : "SvgLayer1", + "entityKind" : "Path", + "parentPath" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 70.2, + "computedY" : 674.89, + "placementX" : 70.2, + "placementY" : 674.89, + "placementWidth" : 98.0, + "placementHeight" : 98.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 98.0, + "contentHeight" : 98.0, + "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" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]/SvgLayer2[2]", + "entityName" : "SvgLayer2", + "entityKind" : "Path", + "parentPath" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]", + "childIndex" : 2, + "depth" : 6, + "layer" : 6, + "computedX" : 70.2, + "computedY" : 674.89, + "placementX" : 70.2, + "placementY" : 674.89, + "placementWidth" : 98.0, + "placementHeight" : 98.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 98.0, + "contentHeight" : 98.0, + "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" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]/SvgLayer3[3]", + "entityName" : "SvgLayer3", + "entityKind" : "Path", + "parentPath" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]", + "childIndex" : 3, + "depth" : 6, + "layer" : 6, + "computedX" : 70.2, + "computedY" : 674.89, + "placementX" : 70.2, + "placementY" : 674.89, + "placementWidth" : 98.0, + "placementHeight" : 98.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 98.0, + "contentHeight" : 98.0, + "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" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]/SvgLayer4[4]", + "entityName" : "SvgLayer4", + "entityKind" : "Path", + "parentPath" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]", + "childIndex" : 4, + "depth" : 6, + "layer" : 6, + "computedX" : 70.2, + "computedY" : 674.89, + "placementX" : 70.2, + "placementY" : 674.89, + "placementWidth" : 98.0, + "placementHeight" : 98.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 98.0, + "contentHeight" : 98.0, + "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" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]/SvgLayer5[5]", + "entityName" : "SvgLayer5", + "entityKind" : "Path", + "parentPath" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]", + "childIndex" : 5, + "depth" : 6, + "layer" : 6, + "computedX" : 70.2, + "computedY" : 674.89, + "placementX" : 70.2, + "placementY" : 674.89, + "placementWidth" : 98.0, + "placementHeight" : 98.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 98.0, + "contentHeight" : 98.0, + "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" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]/SvgLayer6[6]", + "entityName" : "SvgLayer6", + "entityKind" : "Path", + "parentPath" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]", + "childIndex" : 6, + "depth" : 6, + "layer" : 6, + "computedX" : 70.2, + "computedY" : 674.89, + "placementX" : 70.2, + "placementY" : 674.89, + "placementWidth" : 98.0, + "placementHeight" : 98.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 98.0, + "contentHeight" : 98.0, + "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" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]/SvgLayer7[7]", + "entityName" : "SvgLayer7", + "entityKind" : "Path", + "parentPath" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]", + "childIndex" : 7, + "depth" : 6, + "layer" : 6, + "computedX" : 70.2, + "computedY" : 674.89, + "placementX" : 70.2, + "placementY" : 674.89, + "placementWidth" : 98.0, + "placementHeight" : 98.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 98.0, + "contentHeight" : 98.0, + "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" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]/SvgLayer8[8]", + "entityName" : "SvgLayer8", + "entityKind" : "Path", + "parentPath" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]", + "childIndex" : 8, + "depth" : 6, + "layer" : 6, + "computedX" : 70.2, + "computedY" : 674.89, + "placementX" : 70.2, + "placementY" : 674.89, + "placementWidth" : 98.0, + "placementHeight" : 98.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 98.0, + "contentHeight" : 98.0, + "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" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]/SvgLayer9[9]", + "entityName" : "SvgLayer9", + "entityKind" : "Path", + "parentPath" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]", + "childIndex" : 9, + "depth" : 6, + "layer" : 6, + "computedX" : 70.2, + "computedY" : 674.89, + "placementX" : 70.2, + "placementY" : 674.89, + "placementWidth" : 98.0, + "placementHeight" : 98.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 98.0, + "contentHeight" : 98.0, + "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" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]/SvgLayer10[10]", + "entityName" : "SvgLayer10", + "entityKind" : "Path", + "parentPath" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]", + "childIndex" : 10, + "depth" : 6, + "layer" : 6, + "computedX" : 70.2, + "computedY" : 674.89, + "placementX" : 70.2, + "placementY" : 674.89, + "placementWidth" : 98.0, + "placementHeight" : 98.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 98.0, + "contentHeight" : 98.0, + "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" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]/SvgLayer11[11]", + "entityName" : "SvgLayer11", + "entityKind" : "Path", + "parentPath" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]", + "childIndex" : 11, + "depth" : 6, + "layer" : 6, + "computedX" : 70.2, + "computedY" : 674.89, + "placementX" : 70.2, + "placementY" : 674.89, + "placementWidth" : 98.0, + "placementHeight" : 98.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 98.0, + "contentHeight" : 98.0, + "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" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]/SvgLayer12[12]", + "entityName" : "SvgLayer12", + "entityKind" : "Path", + "parentPath" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]", + "childIndex" : 12, + "depth" : 6, + "layer" : 6, + "computedX" : 70.2, + "computedY" : 674.89, + "placementX" : 70.2, + "placementY" : 674.89, + "placementWidth" : 98.0, + "placementHeight" : 98.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 98.0, + "contentHeight" : 98.0, + "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" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]/SvgLayer13[13]", + "entityName" : "SvgLayer13", + "entityKind" : "Path", + "parentPath" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]", + "childIndex" : 13, + "depth" : 6, + "layer" : 6, + "computedX" : 70.2, + "computedY" : 674.89, + "placementX" : 70.2, + "placementY" : 674.89, + "placementWidth" : 98.0, + "placementHeight" : 98.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 98.0, + "contentHeight" : 98.0, + "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" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]/SvgLayer14[14]", + "entityName" : "SvgLayer14", + "entityKind" : "Path", + "parentPath" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]", + "childIndex" : 14, + "depth" : 6, + "layer" : 6, + "computedX" : 70.2, + "computedY" : 674.89, + "placementX" : 70.2, + "placementY" : 674.89, + "placementWidth" : 98.0, + "placementHeight" : 98.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 98.0, + "contentHeight" : 98.0, + "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" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]/SvgLayer15[15]", + "entityName" : "SvgLayer15", + "entityKind" : "Path", + "parentPath" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]", + "childIndex" : 15, + "depth" : 6, + "layer" : 6, + "computedX" : 70.2, + "computedY" : 674.89, + "placementX" : 70.2, + "placementY" : 674.89, + "placementWidth" : 98.0, + "placementHeight" : 98.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 98.0, + "contentHeight" : 98.0, + "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" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]/SvgLayer16[16]", + "entityName" : "SvgLayer16", + "entityKind" : "Path", + "parentPath" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]", + "childIndex" : 16, + "depth" : 6, + "layer" : 6, + "computedX" : 70.2, + "computedY" : 674.89, + "placementX" : 70.2, + "placementY" : 674.89, + "placementWidth" : 98.0, + "placementHeight" : 98.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 98.0, + "contentHeight" : 98.0, + "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" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]/SvgLayer17[17]", + "entityName" : "SvgLayer17", + "entityKind" : "Path", + "parentPath" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]", + "childIndex" : 17, + "depth" : 6, + "layer" : 6, + "computedX" : 70.2, + "computedY" : 674.89, + "placementX" : 70.2, + "placementY" : 674.89, + "placementWidth" : 98.0, + "placementHeight" : 98.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 98.0, + "contentHeight" : 98.0, + "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" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]/SvgLayer18[18]", + "entityName" : "SvgLayer18", + "entityKind" : "Path", + "parentPath" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]", + "childIndex" : 18, + "depth" : 6, + "layer" : 6, + "computedX" : 70.2, + "computedY" : 674.89, + "placementX" : 70.2, + "placementY" : 674.89, + "placementWidth" : 98.0, + "placementHeight" : 98.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 98.0, + "contentHeight" : 98.0, + "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" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]/SvgLayer19[19]", + "entityName" : "SvgLayer19", + "entityKind" : "Path", + "parentPath" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]", + "childIndex" : 19, + "depth" : 6, + "layer" : 6, + "computedX" : 70.2, + "computedY" : 674.89, + "placementX" : 70.2, + "placementY" : 674.89, + "placementWidth" : 98.0, + "placementHeight" : 98.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 98.0, + "contentHeight" : 98.0, + "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" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]/SvgLayer20[20]", + "entityName" : "SvgLayer20", + "entityKind" : "Path", + "parentPath" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]", + "childIndex" : 20, + "depth" : 6, + "layer" : 6, + "computedX" : 70.2, + "computedY" : 674.89, + "placementX" : 70.2, + "placementY" : 674.89, + "placementWidth" : 98.0, + "placementHeight" : 98.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 98.0, + "contentHeight" : 98.0, + "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" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]/SvgLayer21[21]", + "entityName" : "SvgLayer21", + "entityKind" : "Path", + "parentPath" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]", + "childIndex" : 21, + "depth" : 6, + "layer" : 6, + "computedX" : 70.2, + "computedY" : 674.89, + "placementX" : 70.2, + "placementY" : 674.89, + "placementWidth" : 98.0, + "placementHeight" : 98.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 98.0, + "contentHeight" : 98.0, + "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" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]/SvgLayer22[22]", + "entityName" : "SvgLayer22", + "entityKind" : "Path", + "parentPath" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]", + "childIndex" : 22, + "depth" : 6, + "layer" : 6, + "computedX" : 70.2, + "computedY" : 674.89, + "placementX" : 70.2, + "placementY" : 674.89, + "placementWidth" : 98.0, + "placementHeight" : 98.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 98.0, + "contentHeight" : 98.0, + "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" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]/SvgLayer23[23]", + "entityName" : "SvgLayer23", + "entityKind" : "Path", + "parentPath" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]", + "childIndex" : 23, + "depth" : 6, + "layer" : 6, + "computedX" : 70.2, + "computedY" : 674.89, + "placementX" : 70.2, + "placementY" : 674.89, + "placementWidth" : 98.0, + "placementHeight" : 98.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 98.0, + "contentHeight" : 98.0, + "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" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]/SvgLayer24[24]", + "entityName" : "SvgLayer24", + "entityKind" : "Path", + "parentPath" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]", + "childIndex" : 24, + "depth" : 6, + "layer" : 6, + "computedX" : 70.2, + "computedY" : 674.89, + "placementX" : 70.2, + "placementY" : 674.89, + "placementWidth" : 98.0, + "placementHeight" : 98.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 98.0, + "contentHeight" : 98.0, + "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" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]/SvgLayer25[25]", + "entityName" : "SvgLayer25", + "entityKind" : "Path", + "parentPath" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]", + "childIndex" : 25, + "depth" : 6, + "layer" : 6, + "computedX" : 70.2, + "computedY" : 674.89, + "placementX" : 70.2, + "placementY" : 674.89, + "placementWidth" : 98.0, + "placementHeight" : 98.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 98.0, + "contentHeight" : 98.0, + "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" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]/SvgLayer26[26]", + "entityName" : "SvgLayer26", + "entityKind" : "Path", + "parentPath" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]", + "childIndex" : 26, + "depth" : 6, + "layer" : 6, + "computedX" : 70.2, + "computedY" : 674.89, + "placementX" : 70.2, + "placementY" : 674.89, + "placementWidth" : 98.0, + "placementHeight" : 98.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 98.0, + "contentHeight" : 98.0, + "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" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]/SvgLayer27[27]", + "entityName" : "SvgLayer27", + "entityKind" : "Path", + "parentPath" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]", + "childIndex" : 27, + "depth" : 6, + "layer" : 6, + "computedX" : 70.2, + "computedY" : 674.89, + "placementX" : 70.2, + "placementY" : 674.89, + "placementWidth" : 98.0, + "placementHeight" : 98.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 98.0, + "contentHeight" : 98.0, + "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" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]/SvgLayer28[28]", + "entityName" : "SvgLayer28", + "entityKind" : "Path", + "parentPath" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]", + "childIndex" : 28, + "depth" : 6, + "layer" : 6, + "computedX" : 70.2, + "computedY" : 674.89, + "placementX" : 70.2, + "placementY" : 674.89, + "placementWidth" : 98.0, + "placementHeight" : 98.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 98.0, + "contentHeight" : 98.0, + "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" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]/SvgLayer29[29]", + "entityName" : "SvgLayer29", + "entityKind" : "Path", + "parentPath" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]", + "childIndex" : 29, + "depth" : 6, + "layer" : 6, + "computedX" : 70.2, + "computedY" : 674.89, + "placementX" : 70.2, + "placementY" : 674.89, + "placementWidth" : 98.0, + "placementHeight" : 98.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 98.0, + "contentHeight" : 98.0, + "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" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]/SvgLayer30[30]", + "entityName" : "SvgLayer30", + "entityKind" : "Path", + "parentPath" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]", + "childIndex" : 30, + "depth" : 6, + "layer" : 6, + "computedX" : 70.2, + "computedY" : 674.89, + "placementX" : 70.2, + "placementY" : 674.89, + "placementWidth" : 98.0, + "placementHeight" : 98.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 98.0, + "contentHeight" : 98.0, + "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" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]/SvgLayer31[31]", + "entityName" : "SvgLayer31", + "entityKind" : "Path", + "parentPath" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]", + "childIndex" : 31, + "depth" : 6, + "layer" : 6, + "computedX" : 70.2, + "computedY" : 674.89, + "placementX" : 70.2, + "placementY" : 674.89, + "placementWidth" : 98.0, + "placementHeight" : 98.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 98.0, + "contentHeight" : 98.0, + "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" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]/SvgLayer32[32]", + "entityName" : "SvgLayer32", + "entityKind" : "Path", + "parentPath" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]", + "childIndex" : 32, + "depth" : 6, + "layer" : 6, + "computedX" : 70.2, + "computedY" : 674.89, + "placementX" : 70.2, + "placementY" : 674.89, + "placementWidth" : 98.0, + "placementHeight" : 98.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 98.0, + "contentHeight" : 98.0, + "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" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]/SvgLayer33[33]", + "entityName" : "SvgLayer33", + "entityKind" : "Path", + "parentPath" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]", + "childIndex" : 33, + "depth" : 6, + "layer" : 6, + "computedX" : 70.2, + "computedY" : 674.89, + "placementX" : 70.2, + "placementY" : 674.89, + "placementWidth" : 98.0, + "placementHeight" : 98.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 98.0, + "contentHeight" : 98.0, + "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" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]/SvgLayer34[34]", + "entityName" : "SvgLayer34", + "entityKind" : "Path", + "parentPath" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]", + "childIndex" : 34, + "depth" : 6, + "layer" : 6, + "computedX" : 70.2, + "computedY" : 674.89, + "placementX" : 70.2, + "placementY" : 674.89, + "placementWidth" : 98.0, + "placementHeight" : 98.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 98.0, + "contentHeight" : 98.0, + "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" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]/SvgLayer35[35]", + "entityName" : "SvgLayer35", + "entityKind" : "Path", + "parentPath" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]", + "childIndex" : 35, + "depth" : 6, + "layer" : 6, + "computedX" : 70.2, + "computedY" : 674.89, + "placementX" : 70.2, + "placementY" : 674.89, + "placementWidth" : 98.0, + "placementHeight" : 98.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 98.0, + "contentHeight" : 98.0, + "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" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]/SvgLayer36[36]", + "entityName" : "SvgLayer36", + "entityKind" : "Path", + "parentPath" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/SidebarPortraitPhoto[0]/SvgIcon[0]", + "childIndex" : 36, + "depth" : 6, + "layer" : 6, + "computedX" : 70.2, + "computedY" : 674.89, + "placementX" : 70.2, + "placementY" : 674.89, + "placementWidth" : 98.0, + "placementHeight" : 98.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 98.0, + "contentHeight" : 98.0, + "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" : "SidebarPortraitRoot[0]/SidebarPortraitBodyRow[0]/SidebarPortraitBodySidebar[0]/ParagraphNode[1]", "entityName" : null, @@ -142,9 +1282,9 @@ "depth" : 4, "layer" : 4, "computedX" : 41.0, - "computedY" : 644.396, + "computedY" : 635.89, "placementX" : 41.0, - "placementY" : 644.396, + "placementY" : 635.89, "placementWidth" : 83.206, "placementHeight" : 10.0, "startPage" : 0, @@ -172,9 +1312,9 @@ "depth" : 4, "layer" : 4, "computedX" : 41.0, - "computedY" : 622.396, + "computedY" : 613.89, "placementX" : 41.0, - "placementY" : 622.396, + "placementY" : 613.89, "placementWidth" : 78.375, "placementHeight" : 10.0, "startPage" : 0, @@ -202,9 +1342,9 @@ "depth" : 4, "layer" : 4, "computedX" : 41.0, - "computedY" : 600.396, + "computedY" : 591.89, "placementX" : 41.0, - "placementY" : 600.396, + "placementY" : 591.89, "placementWidth" : 57.459, "placementHeight" : 10.0, "startPage" : 0, @@ -232,9 +1372,9 @@ "depth" : 4, "layer" : 4, "computedX" : 41.0, - "computedY" : 578.396, + "computedY" : 569.89, "placementX" : 41.0, - "placementY" : 578.396, + "placementY" : 569.89, "placementWidth" : 45.341, "placementHeight" : 10.0, "startPage" : 0, @@ -262,9 +1402,9 @@ "depth" : 4, "layer" : 4, "computedX" : 41.0, - "computedY" : 556.396, + "computedY" : 547.89, "placementX" : 41.0, - "placementY" : 556.396, + "placementY" : 547.89, "placementWidth" : 40.909, "placementHeight" : 10.0, "startPage" : 0, @@ -292,9 +1432,9 @@ "depth" : 4, "layer" : 4, "computedX" : 41.0, - "computedY" : 534.396, + "computedY" : 525.89, "placementX" : 41.0, - "placementY" : 534.396, + "placementY" : 525.89, "placementWidth" : 50.0, "placementHeight" : 1.0, "startPage" : 0, @@ -322,9 +1462,9 @@ "depth" : 4, "layer" : 4, "computedX" : 41.0, - "computedY" : 505.436, + "computedY" : 496.93, "placementX" : 41.0, - "placementY" : 505.436, + "placementY" : 496.93, "placementWidth" : 84.845, "placementHeight" : 12.96, "startPage" : 0, @@ -352,9 +1492,9 @@ "depth" : 4, "layer" : 4, "computedX" : 41.0, - "computedY" : 480.356, + "computedY" : 471.85, "placementX" : 41.0, - "placementY" : 480.356, + "placementY" : 471.85, "placementWidth" : 103.555, "placementHeight" : 10.08, "startPage" : 0, @@ -382,9 +1522,9 @@ "depth" : 4, "layer" : 4, "computedX" : 41.0, - "computedY" : 461.996, + "computedY" : 453.49, "placementX" : 41.0, - "placementY" : 461.996, + "placementY" : 453.49, "placementWidth" : 111.033, "placementHeight" : 9.36, "startPage" : 0, @@ -412,9 +1552,9 @@ "depth" : 4, "layer" : 4, "computedX" : 41.0, - "computedY" : 436.916, + "computedY" : 428.41, "placementX" : 41.0, - "placementY" : 436.916, + "placementY" : 428.41, "placementWidth" : 120.767, "placementHeight" : 10.08, "startPage" : 0, @@ -442,9 +1582,9 @@ "depth" : 4, "layer" : 4, "computedX" : 41.0, - "computedY" : 418.556, + "computedY" : 410.05, "placementX" : 41.0, - "placementY" : 418.556, + "placementY" : 410.05, "placementWidth" : 85.176, "placementHeight" : 9.36, "startPage" : 0, @@ -472,9 +1612,9 @@ "depth" : 4, "layer" : 4, "computedX" : 41.0, - "computedY" : 396.556, + "computedY" : 388.05, "placementX" : 41.0, - "placementY" : 396.556, + "placementY" : 388.05, "placementWidth" : 50.0, "placementHeight" : 1.0, "startPage" : 0, @@ -502,9 +1642,9 @@ "depth" : 4, "layer" : 4, "computedX" : 41.0, - "computedY" : 367.596, + "computedY" : 359.09, "placementX" : 41.0, - "placementY" : 367.596, + "placementY" : 359.09, "placementWidth" : 80.568, "placementHeight" : 12.96, "startPage" : 0, @@ -532,9 +1672,9 @@ "depth" : 4, "layer" : 4, "computedX" : 41.0, - "computedY" : 345.996, + "computedY" : 337.49, "placementX" : 41.0, - "placementY" : 345.996, + "placementY" : 337.49, "placementWidth" : 26.792, "placementHeight" : 9.6, "startPage" : 0, @@ -562,9 +1702,9 @@ "depth" : 4, "layer" : 4, "computedX" : 41.0, - "computedY" : 324.396, + "computedY" : 315.89, "placementX" : 41.0, - "placementY" : 324.396, + "placementY" : 315.89, "placementWidth" : 29.12, "placementHeight" : 9.6, "startPage" : 0, @@ -592,9 +1732,9 @@ "depth" : 4, "layer" : 4, "computedX" : 41.0, - "computedY" : 302.796, + "computedY" : 294.29, "placementX" : 41.0, - "placementY" : 302.796, + "placementY" : 294.29, "placementWidth" : 24.224, "placementHeight" : 9.6, "startPage" : 0, @@ -622,9 +1762,9 @@ "depth" : 4, "layer" : 4, "computedX" : 41.0, - "computedY" : 281.196, + "computedY" : 272.69, "placementX" : 41.0, - "placementY" : 281.196, + "placementY" : 272.69, "placementWidth" : 36.688, "placementHeight" : 9.6, "startPage" : 0, @@ -652,9 +1792,9 @@ "depth" : 4, "layer" : 4, "computedX" : 41.0, - "computedY" : 259.596, + "computedY" : 251.09, "placementX" : 41.0, - "placementY" : 259.596, + "placementY" : 251.09, "placementWidth" : 87.992, "placementHeight" : 9.6, "startPage" : 0, @@ -682,9 +1822,9 @@ "depth" : 4, "layer" : 4, "computedX" : 41.0, - "computedY" : 237.596, + "computedY" : 229.09, "placementX" : 41.0, - "placementY" : 237.596, + "placementY" : 229.09, "placementWidth" : 50.0, "placementHeight" : 1.0, "startPage" : 0, @@ -712,9 +1852,9 @@ "depth" : 4, "layer" : 4, "computedX" : 41.0, - "computedY" : 208.636, + "computedY" : 200.13, "placementX" : 41.0, - "placementY" : 208.636, + "placementY" : 200.13, "placementWidth" : 85.795, "placementHeight" : 12.96, "startPage" : 0, diff --git a/src/test/resources/visual-baselines/cv-v2-layered/mint_editorial-page-0.png b/src/test/resources/visual-baselines/cv-v2-layered/mint_editorial-page-0.png index 6d16b68d..fa498593 100644 Binary files a/src/test/resources/visual-baselines/cv-v2-layered/mint_editorial-page-0.png and b/src/test/resources/visual-baselines/cv-v2-layered/mint_editorial-page-0.png differ diff --git a/src/test/resources/visual-baselines/cv-v2-layered/mint_editorial-page-1.png b/src/test/resources/visual-baselines/cv-v2-layered/mint_editorial-page-1.png index f747e92b..4d611285 100644 Binary files a/src/test/resources/visual-baselines/cv-v2-layered/mint_editorial-page-1.png and b/src/test/resources/visual-baselines/cv-v2-layered/mint_editorial-page-1.png differ diff --git a/src/test/resources/visual-baselines/cv-v2-layered/monogram_sidebar-page-0.png b/src/test/resources/visual-baselines/cv-v2-layered/monogram_sidebar-page-0.png index 3540def7..ac130620 100644 Binary files a/src/test/resources/visual-baselines/cv-v2-layered/monogram_sidebar-page-0.png and b/src/test/resources/visual-baselines/cv-v2-layered/monogram_sidebar-page-0.png differ diff --git a/src/test/resources/visual-baselines/cv-v2-layered/sidebar_portrait-page-0.png b/src/test/resources/visual-baselines/cv-v2-layered/sidebar_portrait-page-0.png index f66478c3..f6f426f9 100644 Binary files a/src/test/resources/visual-baselines/cv-v2-layered/sidebar_portrait-page-0.png and b/src/test/resources/visual-baselines/cv-v2-layered/sidebar_portrait-page-0.png differ diff --git a/src/test/resources/visual-baselines/cv-v2-layered/timeline_minimal-page-0.png b/src/test/resources/visual-baselines/cv-v2-layered/timeline_minimal-page-0.png index 5a1fd18f..d075ab25 100644 Binary files a/src/test/resources/visual-baselines/cv-v2-layered/timeline_minimal-page-0.png and b/src/test/resources/visual-baselines/cv-v2-layered/timeline_minimal-page-0.png differ diff --git a/src/test/resources/visual-baselines/cv-v2/monogram_sidebar-page-0.png b/src/test/resources/visual-baselines/cv-v2/monogram_sidebar-page-0.png index fa300321..9ffc5e0f 100644 Binary files a/src/test/resources/visual-baselines/cv-v2/monogram_sidebar-page-0.png and b/src/test/resources/visual-baselines/cv-v2/monogram_sidebar-page-0.png differ diff --git a/src/test/resources/visual-baselines/cv-v2/sidebar_portrait-page-0.png b/src/test/resources/visual-baselines/cv-v2/sidebar_portrait-page-0.png index 6e139b6e..8797634b 100644 Binary files a/src/test/resources/visual-baselines/cv-v2/sidebar_portrait-page-0.png and b/src/test/resources/visual-baselines/cv-v2/sidebar_portrait-page-0.png differ diff --git a/src/test/resources/visual-baselines/cv-v2/timeline_minimal-page-0.png b/src/test/resources/visual-baselines/cv-v2/timeline_minimal-page-0.png index 01eaadbe..cb98c1f4 100644 Binary files a/src/test/resources/visual-baselines/cv-v2/timeline_minimal-page-0.png and b/src/test/resources/visual-baselines/cv-v2/timeline_minimal-page-0.png differ