geviert
v0.1.0
Published
Parameterized graphic design on Swiss grid systems: Müller-Brockmann's modular typographic grids, Gerstner's programmes, and seeded, constraint-solved page composition. A page is a constraint.
Maintainers
Readme
Geviert
A page is a constraint, not a canvas.
See it compose itself
geviert.melissawiederrecht.com — a real book, composed in your browser as the page loads. Reroll the seed, roll a new grid, show the grid. The text is live: select it, copy it, search it. Every baseline is measured, not computed, and lands within 0.013 px of the lattice.
das Geviert — the em quad, typography's square module, from the old German verb vieren, "to make four." Pronounced geh-FEERT.
A permissive open-source library for parameterized graphic design on grid systems
in the Swiss tradition: Karl Gerstner (Designing Programmes, 1964) and Josef
Müller-Brockmann (Grid Systems in Graphic Design, 1981). Built from a full
reading of the sources (see research/); the canon is encoded as executable
rules and the test suite reproduces the books' own numbers.
Gerstner's thesis is the API: instead of solutions for problems, programmes for solutions. You author constraints; the library enumerates every layout that satisfies them; a seed picks one. Deterministic: same programme + same seed → same layout, forever.
Is this for you?
Read this before anything else, because it will either delight you or send you away, and both are fine.
There is no way to express an off-grid position. Not discouraged — no syntax for it. A picture is a rectangle of whole grid fields; a title stands on whole body lines; a chart's bars are whole lines tall. If your work needs a box nudged 3 pixels left, you need a different tool, and you'll know within one paragraph of the docs rather than on day two.
What that refusal buys: because every legal page is built from countable parts, the set of valid layouts is finite — so layout becomes a search, taste becomes a small object of named weights you can read and change, and a seed can reproduce any book forever. This is a designer in a library: opinionated, citable, deterministic. It is not a layout engine that does what it's told.
If you want: seeded editorial layouts for generative art, print-quality PDFs from data, browser-native paginated documents with live selectable text, or the Swiss canon as an executable specification — you're home.
And to be equally plain about what this is not: it is a page composition engine, print-shaped, composing fixed-size leaves. It is not CSS Grid for your website, it does not lay out existing DOM, and it will not make your app responsive. If "grid" brought you here from web layout, this is the other, older meaning of the word.
Quickstart
npm install geviertThe shortest path is a named programme — a complete commission (grid, registered type system, setting) you can override key by key:
import { composeDocument, programmes, renderPage } from 'geviert';
const { pages } = composeDocument({
...programmes.book(), // MB's own A4 page; also: essay, booklet
story: myParagraphs,
seed: 'first',
});
const svg = renderPage({ grid: pages[0].grid, items: pages[0].items });The full commission, written by hand:
import { composeDocument, grid, style, typeSystem, renderPage } from 'geviert';
import { registerFontFile } from 'geviert/font-file'; // optional peer: opentype.js
await registerFontFile('inter', './Inter-Regular.ttf'); // any TTF/OTF/WOFF, one call
const T = typeSystem({ module: 12, roles: {
body: style(9, 3, { font: 'inter' }),
chapter: style(17, 7, { font: 'inter', weight: 'bold' }),
caption: style(7, 5, { font: 'inter' }),
}}).roles; // refuses, helpfully, if it can't register
const { pages } = composeDocument({
grid: ({ side }) => grid({ page: 'A4',
margins: { inner: 42, head: 46, outer: 50, tail: 78 },
columns: 4, columnGutter: 13, line: T.body.lineHeight, rows: 8, side }),
styles: T,
story: [
{ text: 'ONE', role: 'chapter', rule: true, spaceAfter: 1 },
'A paragraph. Another sentence of it, with a picture nearby (Fig 1).',
],
figures: [{ anchor: '(Fig 1)', sizes: [{ cols: 2, rows: 2 }], href: 'photo.jpg',
caption: 'Fig 1. Sized in fields, placed by search.' }],
seed: 'any-string',
});
const svg = renderPage({ grid: pages[0].grid, items: pages[0].items });Ships with TypeScript types for the whole surface. Composes ~30 pages/second
with figures, justification and Knuth–Plass on an ordinary laptop (280-page
benchmark: node tools/bench-book.mjs).
The pieces
- Units & paper — Didot points, ciceros, mm; ISO 216 exact (MB pp.16-18)
- Type area (
typearea.js) — margin canons (MB 1:1.5:2:3, golden 3:5:5:8), half-page-area construction, craft advisories (MB pp.39-56) - Modular grid (
grid.js) — the MB grid: field height = whole lines, gutters = whole empty lines, k·m+(k−1)·g = N division arithmetic; ships the three dimensioned A4 reference grids (8/20/32 fields, MB pp.73/77/88) as presets - Mobile grid (
mobile.js) — Gerstner's compound grid: one square area in line-units carrying simultaneous column divisions;gerstnerNumber()finds the minimal unit count for your divisions (58 for 1..6 at gutter 2) - Programme (
programme.js) — constraints DSL + exhaustive backtracking solver + seeded selection with taste scoring;solutions(),layout(seed),reroll(seed, {lock}) - SVG renderer (
render/svg.js) — grids, layouts, and mobile-grid figures drawn in the book's own conventions (red hairlines, grey blocks, the proportional diagonal) - Typography (
type.js) — styles, AFM metrics (Helvetica/Times), ragged line breaking, andtypeSystem()enforcing MB's cross-size register: every style's line height must meet the shared module (24pt canon) - Typeset pages (
render/page.js) — text flowed into fields on the baseline lock (last line stands on the field bottom, MB p.58), images clipped to field rects (or deterministic placeholders), rules and blocks - Placement helpers (
helpers.js) — folio placement per MB pp.42-44/68, bottom-anchored captions (p.108), subtitle slots (p.106) - Mobile programmes (
mobileProgramme.js) — the solver over Gerstner's compound grid, with column division and row division chosen independently per item (2-division wide, 6-division deep) — cross-division layouts nobody else ships
Quick taste
import { presets, programme, field, align, below, whitespace, renderSVG } from 'geviert';
const p = programme({
grid: presets.mb20(), // MB's dimensioned 20-field A4 grid
content: {
headline: { cols: [2, 4], rows: 1 },
image: { cols: [2, 3], rows: [2, 3] },
body: { cols: 1, rows: [2, 3] },
caption: { cols: 1, rows: 1 },
},
constraints: [
field('headline').inBand('top', 1 / 5),
below('image', 'headline'),
align('caption', 'image').leftEdge(),
below('caption', 'image'),
whitespace().atLeast(0.15),
],
});
const layout = p.layout('edition-7'); // seed → layout, deterministic
const svg = renderSVG(layout); // book-style drawing
const variants = p.solutions({ limit: 100 }); // or enumerate the space
const re = p.reroll('edition-8', { from: layout, lock: ['headline'] });import { mobileGrid, gerstnerNumber } from 'geviert';
gerstnerNumber([1, 2, 3, 4, 5, 6], 2); // → 58. That's why Capital was 58 units.
const mg = mobileGrid({ unit: 10, units: 58 });
mg.field(4, 1, 0, 2, 3); // a field on the 4-column division- Morphological box (
box.js) — Gerstner's enumerable parameter space (KG pp.58-60), with his own typogram box shipped verbatim asGERSTNER_TYPOGRAM;pick(seed)chooses style chains the waylayout(seed)chooses placements - Caption system —
captionFit()/gutterCaption()put captions IN the row gutter the grid was constructed to hold (MB p.63);figure()bundles picture + caption into one whole-line extent nothing can crowd - Real fonts —
registerFont()takes exact widths/metrics + optional embedded data URI;tools/font-metrics.mjsextracts everything from a TTF/OTF (via opentype.js)
Running
npm test # canon tests — assertions cite book pages
npm run examples # examples/out/: grids, KG p.61 figure, contact sheet,
# plate reproductions, 6 typeset editions, mobile poster
npm run build # dist/geviert.js (+ .min) browser bundle
node tools/svg-to-pdf.mjs in.svg out.pdf # print-ready PDF at true sizeEscape hatches and honest edges
- Taste constants are named options with cited defaults, not magic: the justify elastic limits, caption air, title-rule position, pull-quote sizing, the anchor pull, the whole harmony object. Disagree with a number? Pass your own.
- Register errors teach: break the module and the error names the nearest legal settings for your size instead of just refusing.
- Optional peers, all opt-in, core stays zero-dependency:
opentype.js(font files at runtime),hyphen(TeX hyphenation patterns),@chenglou/pretext(30× faster greedy breaking, eval script included). - Book furniture ships: running heads (
runningHead), footnotes (noteson a paragraph — set at the foot of the column, never split across pages, refused with arithmetic when they cannot stand), generated contents (report.contents+contentsLeaf()), and cross-references ({page:id}tokens, resolved by recomposition until stable). - Right-to-left setting ships (
direction: 'rtl'), with one honest condition: joining scripts (Arabic, Syriac) must be measured shaped, so composition happens in a browser or headless Chrome afteruseBrowserFonts(). Measuring Arabic from width tables is refused with a teaching error, not composed wrong. - Justification is by elastic word spaces. Kashida justification is not here yet: proper elongation needs per-typeface knowledge of legal insertion points. The Arabic essay on the site discloses this in print.
- Not here (yet): tables, line/pie charts, vertical setting, facing-page balance. The canon chapters on exhibition systems and corporate identity are out of scope on purpose.
Status
0.1.0. The library is complete for what it claims and proven by the books on the site's shelf, including a manual it composes about itself. APIs may still move while the version starts with a zero. Maintained as time allows, by one person, with care.
License: MIT. Image sources for every book: CREDITS.md.

