feat(cv): qualify templates and reliable local import processing
This commit is contained in:
@@ -24,6 +24,7 @@ public sealed class ThemedCvRenderer : IThemedCvRenderer
|
||||
public ThemedCvRenderResult Render(CvRenderModel model, CvTheme theme, CvVariantSettings settings)
|
||||
{
|
||||
settings = CvVariantSettingsJson.Normalize(settings);
|
||||
if (settings.DesignVersion == 2) theme = CvThemeCatalog.Resolve(theme.Id, settings.DesignVersion);
|
||||
var accent = Override(settings.AccentColor, theme.Accent);
|
||||
var headerInk = ContrastInk(accent);
|
||||
var ink = Override(settings.TextColor, theme.Ink);
|
||||
@@ -238,6 +239,12 @@ public sealed class ThemedCvRenderer : IThemedCvRenderer
|
||||
var headingSize = settings.HeadingSizePt ?? t.HeadingSizePt;
|
||||
var lineHeight = settings.LineHeight ?? t.LineHeight;
|
||||
var sidebarWidth = settings.SidebarWidthMm ?? t.SidebarWidthMm;
|
||||
var sidebarPadding = settings.DesignVersion == 2 ? "8" : margin;
|
||||
var sidebarBackground = settings.DesignVersion == 2 ? "var(--cv-sidebar-bg)" : "var(--cv-accent-color)";
|
||||
var sidebarInk = settings.DesignVersion == 2 ? "var(--cv-sidebar-ink)" : "var(--cv-accent-ink)";
|
||||
var sidebarVariables = settings.DesignVersion == 2 ? $"--cv-sidebar-bg:{t.SidebarBg};--cv-sidebar-ink:{t.SidebarInk};" : string.Empty;
|
||||
var sidebarDetailCss = settings.DesignVersion == 2
|
||||
? ".sidebar .bullets li::marker,.sidebar .skill-group-name{color:var(--cv-sidebar-ink);}" : string.Empty;
|
||||
var headingCss = headingStyle switch
|
||||
{
|
||||
"underline" => ".section-title{border-bottom:1.5px solid var(--cv-accent-soft);padding-bottom:1.5mm;}",
|
||||
@@ -250,13 +257,13 @@ public sealed class ThemedCvRenderer : IThemedCvRenderer
|
||||
: $"{F(sidebarWidth)}mm minmax(0,1fr)";
|
||||
var layoutCss = twoColumn
|
||||
? $@".cols{{display:grid;grid-template-columns:{columnTemplate};min-height:{page.h};}}
|
||||
.sidebar{{background:var(--cv-accent-color);color:var(--cv-accent-ink);padding:{margin}mm;}}
|
||||
.sidebar .section-title{{color:var(--cv-accent-ink);border-color:color-mix(in srgb,var(--cv-accent-ink) 38%,transparent);}}
|
||||
.sidebar .tag{{border-color:color-mix(in srgb,var(--cv-accent-ink) 45%,transparent);}}
|
||||
.sidebar .contact,.sidebar .headline,.sidebar .entry-meta,.sidebar .entry-subtitle{{color:var(--cv-accent-ink);}}
|
||||
.sidebar{{background:{sidebarBackground};color:{sidebarInk};padding:{sidebarPadding}mm;}}
|
||||
.sidebar .section-title{{color:{sidebarInk};border-color:color-mix(in srgb,{sidebarInk} 38%,transparent);}}
|
||||
.sidebar .tag{{border-color:color-mix(in srgb,{sidebarInk} 45%,transparent);}}
|
||||
.sidebar .contact,.sidebar .headline,.sidebar .entry-meta,.sidebar .entry-subtitle{{color:{sidebarInk};}}
|
||||
.sidebar a{{color:inherit;}}
|
||||
.main{{padding:{margin}mm;}}
|
||||
.hero .name{{color:var(--cv-accent-ink);}}"
|
||||
.hero .name{{color:{sidebarInk};}}"
|
||||
: $@".main{{padding:0 {margin}mm {margin}mm {margin}mm;}}
|
||||
.header{{padding:{margin}mm {margin}mm {F(t.SectionGapMm * density)}mm {margin}mm;display:flex;gap:6mm;align-items:center;}}
|
||||
.header-band{{background:{accent};color:{headerInk};}}
|
||||
@@ -269,9 +276,20 @@ public sealed class ThemedCvRenderer : IThemedCvRenderer
|
||||
? $@".main{{counter-reset:cv-section;}} .main>.section>.section-title{{counter-increment:cv-section;font-family:{CvDocumentFonts.Stack(t.UtilityFont)};font-weight:600;}} .main>.section>.section-title::before{{content:counter(cv-section,decimal-leading-zero) ' — ';color:var(--cv-accent-color);}}"
|
||||
: string.Empty;
|
||||
|
||||
var revisedCss = settings.DesignVersion == 2
|
||||
? $".header .name{{margin-bottom:{F(t.NameGapMm * density)}mm;}}.header + .main{{padding-top:{F(t.HeaderContentGapMm * density)}mm;}}" : string.Empty;
|
||||
if (settings.DesignVersion == 2 && twoColumn)
|
||||
{
|
||||
var edge = layout == "sidebar-right" ? "right" : "left";
|
||||
// Margin boxes paint the sidebar bleed without changing the repeated text safe area.
|
||||
revisedCss += $"@page{{@top-{edge}{{content:\"\";background:{t.SidebarBg};width:{F(sidebarWidth)}mm;}}@bottom-{edge}{{content:\"\";background:{t.SidebarBg};width:{F(sidebarWidth)}mm;}}}}" +
|
||||
$"body::before{{content:\"\";position:fixed;{edge}:0;top:0;bottom:0;width:{F(sidebarWidth)}mm;background:{t.SidebarBg};z-index:0;}}" +
|
||||
".page{position:relative;background:transparent;}.cols{position:relative;z-index:1;}.main{background:var(--cv-paper);}.sidebar{background:transparent;}";
|
||||
}
|
||||
|
||||
return $@"
|
||||
*{{box-sizing:border-box;}}
|
||||
:root{{--cv-accent-color:{accent};--cv-accent-ink:{headerInk};--cv-accent-soft:color-mix(in srgb,{accent} 28%,transparent);--cv-ink:{ink};--cv-muted:{muted};--cv-paper:{paper};--cv-page-margin:{margin}mm;}}
|
||||
:root{{--cv-accent-color:{accent};--cv-accent-ink:{headerInk};--cv-accent-soft:color-mix(in srgb,{accent} 28%,transparent);--cv-ink:{ink};--cv-muted:{muted};--cv-paper:{paper};--cv-page-margin:{margin}mm;{sidebarVariables}}}
|
||||
html,body{{min-width:0;}}
|
||||
body{{margin:0;background:#e9edf2;color:var(--cv-ink);font-family:{bodyFont};font-size:{F(bodySize)}pt;line-height:{F(lineHeight)};-webkit-print-color-adjust:exact;print-color-adjust:exact;}}
|
||||
.page{{width:{page.w};min-height:{page.h};margin:0 auto;background:var(--cv-paper);overflow:visible;overflow-wrap:anywhere;word-break:normal;}}
|
||||
@@ -325,7 +343,7 @@ h1,h2{{font-family:{headingFont};}}
|
||||
.entry-meta{{color:{muted};font-size:{F(bodySize - 0.5)}pt;white-space:normal;text-align:right;max-width:100%;overflow-wrap:anywhere;}}
|
||||
.entry-subtitle{{color:{muted};font-size:{F(bodySize)}pt;margin:.4mm 0 1.2mm 0;}}
|
||||
.entry-tags{{margin-top:1.4mm;}}
|
||||
{layoutCss}
|
||||
{layoutCss}{sidebarDetailCss}
|
||||
{numberedSections}
|
||||
/* Print quality: keep normal entries whole, but allow intentionally classified long entries and
|
||||
long list items to flow. An unsplittable block taller than a page is otherwise clipped. */
|
||||
@@ -349,7 +367,7 @@ body{{background:transparent;}}
|
||||
.main,.sidebar{{padding-top:0;padding-bottom:0;}}
|
||||
}}
|
||||
@page{{size:{page.w} {page.h};margin:{margin}mm 0;}}
|
||||
";
|
||||
" + revisedCss;
|
||||
}
|
||||
|
||||
private static double DensityScale(string? density) => (density ?? "balanced").Trim().ToLowerInvariant() switch
|
||||
@@ -374,7 +392,7 @@ body{{background:transparent;}}
|
||||
|
||||
private static string Override(string? value, string fallback) => string.IsNullOrWhiteSpace(value) ? fallback : value.Trim();
|
||||
|
||||
private static string ContrastInk(string background)
|
||||
internal static string ContrastInk(string background)
|
||||
{
|
||||
if (background.Length != 7 || background[0] != '#' || !background.Skip(1).All(Uri.IsHexDigit)) return "#000";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user