From 8d7adc870617f6acdc22d35cc98d120f8944f1a2 Mon Sep 17 00:00:00 2001 From: Shane Murphy Date: Sun, 14 Jun 2026 21:11:26 +0200 Subject: [PATCH] feat: add preferences.density preset to scale spacing tokens uniformly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds `preferences.density` — a single coherent knob that scales every vertical spacing em-token uniformly, replacing the need for users to hand-tune `bodySize`, `margin`, and individual em-multipliers when they want a tighter (one-page fit) or roomier (print presentation) layout. Three presets: `"compact"` (× 0.85), `"comfortable"` (× 1.0, default), `"spacious"` (× 1.15). Closes #50. The multiplier is threaded through a new `_spacing_scale_state` and applied at every block `above`/`below`, every `v()`, `par.spacing` / `leading`, and `list.spacing`. Text sizes, icon dimensions, and rating-dot geometry are intentionally left alone — density is purely about vertical whitespace, so font-size scaling stays under `bodySize`. Default ("comfortable") is render-identical to a no-density build — verified by re-running `examples/tests/*.pdf` against the tree before and after the change; all byte sizes unchanged. Also fixes a latent issue in `sections/projects.typ` where the description → term gap used a literal `0.6em` and so didn't scale with density, breaking the "matches institution-line → term spacing" invariant the comment claimed. Now uses `0.6 * scale * body-size` to mirror `name()`'s `below`. --- README.md | 1 + examples/tests/density.pdf | Bin 0 -> 75617 bytes internal/header.typ | 14 ++++---- internal/layout.typ | 6 ++++ internal/primitives.typ | 18 +++++++---- internal/state.typ | 17 ++++++++++ lib.typ | 30 +++++++++++------ sections/projects.typ | 14 +++++--- sections/skills.typ | 5 +-- tests/density.typ | 64 +++++++++++++++++++++++++++++++++++++ 10 files changed, 139 insertions(+), 30 deletions(-) create mode 100644 examples/tests/density.pdf create mode 100644 tests/density.typ diff --git a/README.md b/README.md index 37eba6d..9584432 100644 --- a/README.md +++ b/README.md @@ -299,6 +299,7 @@ Every theme, font, layout, and behaviour knob lives in `preferences`. Override a |---|---|---| | `font` | `"Lato"` | Primary font family. Must be installed. | | `bodySize` | `10pt` | Base text size. Sub-elements scale via em-multipliers. | +| `density` | `"comfortable"` | Single knob that scales every vertical-spacing em-token uniformly — block above/below, `v()` gaps, `par` leading/spacing, `list` spacing. `"compact"` (× 0.85) tightens for a one-page fit, `"comfortable"` (× 1.0) is the historical default, `"spacious"` (× 1.15) opens it up for print presentation. Text sizes, icon dimensions, and rating-dot geometry are unaffected — use `bodySize` for those. Unknown values panic. | | `paper` | `"a4"` | Paper size string passed to Typst's `set page(paper: ...)`. `"a4"`, `"us-letter"`, `"a5"`, `"us-legal"`, and the rest of [Typst's named papers](https://typst.app/docs/reference/layout/page/#parameters-paper). | | `margin` | `(x: 0.9cm, y: 1.5cm)` | Page margins. Anything `set page(margin: ...)` accepts. | | `accent` | `palettes.teal` | Theme colour for headings, accent rules, tags, dots. Use a built-in preset — `palettes.{teal,navy,crimson,forest,plum,charcoal}` — or any `rgb(...)` value. | diff --git a/examples/tests/density.pdf b/examples/tests/density.pdf new file mode 100644 index 0000000000000000000000000000000000000000..63fd6d7767727ceef3888eb481ee64cf106cffe1 GIT binary patch literal 75617 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 literal 0 HcmV?d00001 diff --git a/internal/header.typ b/internal/header.typ index bcd5d75..1c9dcc8 100644 --- a/internal/header.typ +++ b/internal/header.typ @@ -5,7 +5,7 @@ // `_format_location` / `_url_encode` helpers that feed the maps deep // link (single-caller helpers, kept adjacent to their consumer). -#import "state.typ": _body_size_state, _accent_state, _emphasis_colour +#import "state.typ": _body_size_state, _accent_state, _spacing_scale_state, _emphasis_colour #import "presets.typ": maps-providers #import "icons.typ": icon, _profile_networks, _network_aliases @@ -146,11 +146,12 @@ context { let body-size = _body_size_state.get() let accent = _accent_state.get() + let scale = _spacing_scale_state.get() let header-text = align(text-align, { block( spacing: 0pt, - below: 1.2 * body-size, + below: 1.2 * scale * body-size, text( 2.5 * body-size, fill: accent, @@ -162,7 +163,7 @@ if "label" in basics and basics.label != none { block( spacing: 0pt, - below: 0.8 * body-size, + below: 0.8 * scale * body-size, text(1.2 * body-size, fill: _emphasis_colour, weight: "bold", basics.label), ) } @@ -308,7 +309,7 @@ // the document width, regardless of how the text above / // below is aligned. let centred-photo = block( - spacing: 0.8 * body-size, + spacing: 0.8 * scale * body-size, width: 100%, align(center, photo), ) @@ -335,7 +336,8 @@ let summary = basics.at("summary", default: none) if summary == none or summary == "" or summary == [] { return } let body-size = _body_size_state.get() - v(0.8 * body-size) + let scale = _spacing_scale_state.get() + v(0.8 * scale * body-size) par(summary) - v(0.4 * body-size) + v(0.4 * scale * body-size) } diff --git a/internal/layout.typ b/internal/layout.typ index dde8b27..cd31888 100644 --- a/internal/layout.typ +++ b/internal/layout.typ @@ -90,6 +90,12 @@ // Every spacing token is an em-multiplier of this, so changing one // knob scales the whole document proportionally. bodySize: 10pt, + // Scales every spacing em-token uniformly (see `_density_scales` + // in `internal/state.typ`). `"comfortable"` (1.0×) preserves the + // historical layout byte-for-byte; `"compact"` (0.85×) tightens + // for one-page CVs; `"spacious"` (1.15×) opens it up for print + // presentation. Text sizes are unaffected. + density: "comfortable", paper: "a4", margin: (x: 0.9cm, y: 1.5cm), // `palettes.teal` — see the `palettes` dict for the curated set diff --git a/internal/primitives.typ b/internal/primitives.typ index e917a64..492f4a1 100644 --- a/internal/primitives.typ +++ b/internal/primitives.typ @@ -3,7 +3,7 @@ // from `lib.typ` for callers composing custom layouts; the leading- // underscore helpers are used internally by the section renderers. -#import "state.typ": _body_size_state, _accent_state, _body_colour, _divider_colour +#import "state.typ": _body_size_state, _accent_state, _spacing_scale_state, _body_colour, _divider_colour #import "icons.typ": icon // Bold accent-coloured line — designed for the company / institution @@ -11,9 +11,10 @@ #let name(body) = context { let body-size = _body_size_state.get() let accent = _accent_state.get() + let scale = _spacing_scale_state.get() block( above: 0pt, - below: 0.6 * body-size, + below: 0.6 * scale * body-size, text(weight: "bold", fill: accent, body), ) } @@ -23,9 +24,10 @@ #let term(period, location: none) = context { if period == none and location == none { return } let body-size = _body_size_state.get() + let scale = _spacing_scale_state.get() block( above: 0pt, - below: 0.8 * body-size, + below: 0.8 * scale * body-size, inset: (left: 0.3 * body-size), text(0.9 * body-size, { if period != none { @@ -77,12 +79,13 @@ #let divider() = context { let body-size = _body_size_state.get() if here().position().y < 2cm { return } - v(0.3 * body-size) + let scale = _spacing_scale_state.get() + v(0.3 * scale * body-size) line( length: 100%, stroke: (paint: _divider_colour, thickness: 0.6pt, dash: "dashed"), ) - v(0.3 * body-size) + v(0.3 * scale * body-size) } // Like `divider()` but with a leading label that sits slightly indented @@ -94,8 +97,9 @@ // the parent section title. #let _labelled_divider(label) = context { let body-size = _body_size_state.get() + let scale = _spacing_scale_state.get() let stroke = (paint: _divider_colour, thickness: 0.6pt, dash: "dashed") - v(0.3 * body-size) + v(0.3 * scale * body-size) pad(left: 0.6 * body-size, grid( columns: (1.3em, auto, 1fr), column-gutter: 0.5 * body-size, @@ -108,7 +112,7 @@ ), line(length: 100%, stroke: stroke), )) - v(0.3 * body-size) + v(0.3 * scale * body-size) } // Interleaves `divider()` between items; the trailing one is suppressed diff --git a/internal/state.typ b/internal/state.typ index 1e148e7..1029111 100644 --- a/internal/state.typ +++ b/internal/state.typ @@ -11,6 +11,23 @@ #let _body_size_state = state("alta-body-size", 10pt) #let _accent_state = state("alta-accent", palettes.teal) #let _max_rating_state = state("alta-max-rating", 5) +// Multiplier applied to every spacing em-token (block above/below, +// `v()`, par.spacing/leading, list.spacing). Driven by +// `preferences.density`; defaults to 1.0 so "comfortable" reproduces +// the historical layout byte-for-byte. +#let _spacing_scale_state = state("alta-spacing-scale", 1.0) + +// `preferences.density` → multiplier on every spacing em-token. +// 0.85 / 1.0 / 1.15 keeps the three presets visibly distinct without +// either crushing lines together or pushing the one-page CV onto two. +// Text sizes, icon dimensions, and rating-dot geometry are +// deliberately left alone — density is purely vertical whitespace, +// so font-size scaling stays the job of `bodySize`. +#let _density_scales = ( + compact: 0.85, + comfortable: 1.0, + spacious: 1.15, +) // Accent is configurable via `alta(preferences: (accent: ...))`; the // rest are opinionated visual constants. diff --git a/lib.typ b/lib.typ index 23d178e..d1dfed4 100644 --- a/lib.typ +++ b/lib.typ @@ -19,7 +19,7 @@ // independent of text size. #import "internal/presets.typ": palettes, maps-providers -#import "internal/state.typ": _body_size_state, _accent_state, _max_rating_state, _body_colour, _emphasis_colour +#import "internal/state.typ": _body_size_state, _accent_state, _max_rating_state, _spacing_scale_state, _density_scales, _body_colour, _emphasis_colour #import "internal/defaults.typ": _default_labels #import "internal/validation.typ": _strict_merge, _check_bool #import "internal/text.typ": _present, styled-link @@ -140,9 +140,19 @@ } let accent = preferences.accent let body-size = preferences.bodySize + let density = preferences.density + if density not in _density_scales { + let quote(k) = "\"" + k + "\"" + panic( + "density must be one of " + _density_scales.keys().map(quote).join(", ") + + ", got: " + repr(density), + ) + } + let scale = _density_scales.at(density) _accent_state.update(accent) _body_size_state.update(body-size) _max_rating_state.update(max-rating) + _spacing_scale_state.update(scale) // PDF metadata is sourced from `basics` (title, author, description) // and the JSON Resume `meta` block (date, keywords). Each optional @@ -191,12 +201,12 @@ margin: preferences.margin, footer: resolved-footer, ) - set par(leading: 0.55em, spacing: 0.7em) + set par(leading: 0.55 * scale * 1em, spacing: 0.7 * scale * 1em) set list( marker: text(0.85em, "•"), indent: 0pt, body-indent: 0.4 * body-size, - spacing: 0.55em, + spacing: 0.55 * scale * 1em, ) // Heading levels map to semantic CV roles: @@ -204,21 +214,21 @@ // === role / qualification line // ==== sub-grouping (publication type) show heading.where(level: 2): it => block(sticky: true)[ - #v(0.6 * body-size) + #v(0.6 * scale * body-size) #text(1.7 * body-size, fill: accent, weight: "bold", upper(it.body)) - #v(-0.7 * body-size) + #v(-0.7 * scale * body-size) #line(length: 100%, stroke: 2pt + accent) - #v(0.2 * body-size) + #v(0.2 * scale * body-size) ] show heading.where(level: 3): it => block( - above: 1.0 * body-size, - below: 0.8 * body-size, + above: 1.0 * scale * body-size, + below: 0.8 * scale * body-size, sticky: true, text(1.2 * body-size, fill: _emphasis_colour, weight: "regular", it.body), ) show heading.where(level: 4): it => block( - above: 0.6 * body-size, - below: 0.6 * body-size, + above: 0.6 * scale * body-size, + below: 0.6 * scale * body-size, sticky: true, text(1.2 * body-size, fill: _emphasis_colour, weight: "bold", it.body), ) diff --git a/sections/projects.typ b/sections/projects.typ index 1a9457f..dfc51e1 100644 --- a/sections/projects.typ +++ b/sections/projects.typ @@ -7,6 +7,7 @@ #import "../internal/text.typ": _present, styled-link #import "../internal/primitives.typ": term, _join_with_dividers, _tag_row #import "../internal/dates.typ": _format_date_range +#import "../internal/state.typ": _spacing_scale_state, _body_size_state #let _projects(entries, labels, prefs) = { let valid = entries.filter(p => _present(p.at("name", default: none))) @@ -19,11 +20,14 @@ if _present(description) { // Softer than `name()` (which is bold + accent) so the // description doesn't compete visually with a linked title. - // Wrapped in a block so the gap to the term row below matches - // the institution-line → term spacing in `_experience` / - // `_education`; using a bare `linebreak()` here leaves no - // paragraph spacing. - block(below: 0.6em, emph(description)) + // Mirrors `name()`'s `below` so the description → term gap + // matches the institution-line → term gap in `_experience` / + // `_education`; literal `0.6em` would skip the density scale + // and break that contract under non-default density. + context block( + below: 0.6 * _spacing_scale_state.get() * _body_size_state.get(), + emph(description), + ) } term(_format_date_range(project, prefs, labels)) for bullet in project.at("highlights", default: ()) [- #bullet] diff --git a/sections/skills.typ b/sections/skills.typ index ac2fc77..02d33ca 100644 --- a/sections/skills.typ +++ b/sections/skills.typ @@ -4,7 +4,7 @@ // `_name_keywords_section`; the two public renderers differ only in // which `labels.*` heading they pass. -#import "../internal/state.typ": _body_size_state +#import "../internal/state.typ": _body_size_state, _spacing_scale_state #import "../internal/primitives.typ": tag, _tag_row // `text("-")` (not `[-]`) — markup-bracketed `-` parses as a list-item @@ -20,7 +20,8 @@ if visible.len() == 0 { return } context { let body-size = _body_size_state.get() - let row-gap = 0.7 * body-size + let scale = _spacing_scale_state.get() + let row-gap = 0.7 * scale * body-size [== #heading] for group in visible { block(above: 0pt, below: row-gap, par(hanging-indent: 1em, leading: row-gap, { diff --git a/tests/density.typ b/tests/density.typ new file mode 100644 index 0000000..bea53a1 --- /dev/null +++ b/tests/density.typ @@ -0,0 +1,64 @@ +// `preferences.density` scales every spacing em-token uniformly so a +// single knob trades vertical breathing room for fit-on-one-page. +// Three pages exercise each preset against the same data; visual diff +// against the default ("comfortable") confirms the multiplier reaches +// section headings, divider rules, par.spacing, the in-section block +// above/below tokens, and the header / summary gaps. + +#import "../lib.typ": alta + +#let cv = ( + basics: ( + name: "Jane Doe", + label: "Senior Software Engineer", + summary: [Backend engineer; the summary block exercises the + pre/post `v()` gaps that flank it.], + email: "jane@example.com", + phone: "+353 1 555 0100", + location: "Dublin, Ireland", + ), + work: ( + ( + name: "Acme Corp", + position: "Senior Software Engineer", + location: "Dublin, Ireland", + startDate: "Jan 2022", + highlights: ([Led the migration.], [Designed the platform.]), + ), + ( + name: "Foo Ltd", + position: "Software Engineer", + startDate: "Jan 2018", + endDate: "Dec 2021", + highlights: ([Shipped the thing.],), + ), + ), + skills: ( + (name: "Languages", keywords: ("Scala", "Python")), + (name: "Infra", keywords: ("Kafka", "AWS", "Kubernetes")), + ), + languages: ( + (language: "English", fluency: "Native"), + (language: "Irish", fluency: "Professional Working"), + ), + education: ( + (institution: "Example U", studyType: "B.Sc.", endDate: "2017"), + ), + certificates: ( + (name: "CKA", issuer: "CNCF"), + ), +) + +// 1. Compact — em spacing tokens × 0.85. Tighter than the default. +#alta(cv, preferences: (density: "compact")) + +#pagebreak() + +// 2. Comfortable — em spacing tokens × 1.0. The default; explicit +// here so the regression is anchored even if the default changes. +#alta(cv, preferences: (density: "comfortable")) + +#pagebreak() + +// 3. Spacious — em spacing tokens × 1.15. Roomier than the default. +#alta(cv, preferences: (density: "spacious"))