mdbook-lini 0.5.0

mdbook preprocessor: every figure from plain text — diagrams, charts, sequences, mindmaps, schematics, and technical drawings, rendered to inline SVG
mdbook-lini-0.5.0 is not a library.

An mdbook preprocessor that compiles ```lini blocks to inline SVG at build time.

```lini
|chart| "Signups by channel" { categories: "Jan", "Feb", "Mar", "Apr", "May", "Jun" } [
  |line| "organic"  { data: 14, 19, 26, 33, 44, 58; curve: smooth; marker: dot; stroke: --teal }
  |line| "referral" { data: 9, 13, 15, 22, 27, 36; curve: smooth; marker: dot; stroke: --purple }
]
```

That is a chart, but the fence is not a chart fence. Lini is one engine for every figure family — flowcharts, charts, sequences, mindmaps, trees, schematics, and technical drawings are all layouts of the same language. So one fence covers all of them, and there is nothing else to install.

  • No runtime. Figures are SVG in the HTML. No JavaScript, no CDN, no browser at build time — the pages work with scripts off.
  • No second binary, no setup. Lini is linked as a library, and the styling ships with the figures. cargo install mdbook-lini plus one line of config is the whole thing.
  • Dark mode for free. Colours are live CSS variables, so figures follow your theme toggle without a re-render.
  • The source is one click away. Each figure carries a small </> toggle that reveals the Lini that drew it, syntax-highlighted at build time — still no JavaScript. A fence word flips it, so a reference chapter can lead with the source instead.
  • Fast and deterministic. A typical diagram compiles in ~2 ms, byte-identically each run.

Install

cargo install mdbook-lini

Setup

One line in book.toml. mdbook finds the mdbook-lini binary from the key, and the styling ships with the figures:

[preprocessor.lini]

That's it — no additional-css, no files to copy.

Writing a figure

Any Lini source works. The tour walks every family and the reference covers it in full; this is the shape of it:

```lini
{ layout: sequence; font-size: 13; }

|icon#reader| "user" { width: 60; stroke: --rose-deep; fill: --rose-wash }
|box#cdn| "CDN" { fill: --sky-wash; stroke: --sky-deep }
|box#origin| "Origin" { fill: --green-wash; stroke: --green-deep }

reader -> cdn "GET /guide"
cdn -> cdn "check edge cache"

|loop| "on miss" { fill: --amber-wash; stroke: --amber-ink } [
  cdn -> origin "fetch"
  origin --> cdn "200 + max-age"
]

cdn --> reader "HTML"
```

A block that references a local image with |image| src: resolves the path against the chapter's own directory.

Blocks in other languages pass through untouched, as does a ```lini block quoted inside a wider fence — so you can document Lini in a Lini-powered book.

Showing the source

Every figure carries a small </> in its top-right corner. Clicking it reveals the Lini that drew it, highlighted with the same vocabulary the VS Code and Zed grammars use — the colouring comes from Lini's own ledger, so a property gets its colour here the moment the language has it.

The toggle is a checkbox and its label — pure CSS, so it costs no JavaScript, takes keyboard focus, and works with scripts off. The listing is your block's own text, verbatim, not reformatted; mdbook's copy button lands on it like any other code block.

It is deliberately not a <details>. Your book's stylesheet is unlayered, so it outranks ours: a <details> gives your theme a second element to frame — a box inside the code block's box — and it carries a disclosure marker your theme can put back however we hide it. With a label there is no marker, and the <pre> is the only element your theme dresses. One frame, like every other code block on the page.

Choosing what a block shows

A block has two views — the figure, and the source that drew it. One word on the fence names them, in the order they appear:

fence shows
```lini the figure, its source folded behind the button
```lini figure the same, said out loud
```lini code the source, the figure folded behind the button
```lini figure-code the figure, then its source — both on the page
```lini code-figure the source, then the figure — both on the page
```lini figure-only the figure, nothing else
```lini code-only the source, nothing else

A single word names the lead and leaves the other view folded — that is the default, and ```lini figure is simply the default spelled out. A compound names both, in order, and drops the toggle: reach for it when the source is the lesson, since a reader meeting the language for the first time should not have to discover a button to see what drew the picture. code-figure is the one a tutorial usually wants — listing first, then the result.

code-only never reaches the compiler. It does not need a rule of its own: a block showing the source and nothing else has no figure to draw, so a fragment, a counter-example or a deliberately broken line stays a highlighted listing instead of becoming an error box.

```lini code-only
|box#hero| "…"   // a shape, not a whole file
```

Whitespace or a comma both separate, so ```lini,figure reads the same. The words are alternatives, each naming a whole arrangement — write two and the last wins. A word we don't recognise is reported on stderr and ignored, never fatal.

Changed in 0.4. figure used to mean the figure alone and now means the figure with its source folded — the default, spelled out; a book that wants the old behaviour writes ```lini figure-only. raw is code-only.

Theming

Each figure is a <div class="lini-figure"> wrapping the SVG. Lini emits every colour as a light-dark() pair keyed on color-scheme, and the shipped styling binds that to mdbook's five built-in themes — which is the entire light/dark integration. A custom theme adds its own class:

html.my-dark-theme .lini { color-scheme: dark; }

To hand Lini your own palette, alias its role variables. Everything shipped sits in @layer, so any unlayered rule of yours wins without !important:

.lini {
    --lini-bg: transparent;
    --lini-fg: var(--fg);
    --lini-accent: #4a7fd4;
    --lini-font-family: var(--body-font);
}

Its eleven-hue palette (--rose, --sky, --teal, … each in five tiers) is emitted only where a figure references it.

[!NOTE] Alias --lini-font-family only to a proportional sans close in metrics to the one Lini measured against at compile time. Lini bakes each label's position and sizes its box to fit, so a wider face — a monospace one especially — pushes the text past its border.

Sizing

The wrapper carries --lini-w, the diagram's natural width. A figure scales down to fit the column but stops at 75% of that width — past there the labels stop reading, so the wrapper scrolls horizontally instead.

The source listing is not bound by that floor: it fills the column and scrolls horizontally on its own when a line is long.

Owning the styling

mdbook-lini's own stylesheet — the wrapper above, the theme binding, the error box, and the source listing's palette — rides along in a <style> block on each chapter that has a figure. Under 3 kB minified, and none on chapters without one. It is not Lini's styling: that lives inside each SVG and travels with it regardless.

To take it over instead, turn it off and link mdbook-lini.css yourself:

[preprocessor.lini]
bundled-css = false

[output.html]
additional-css = ["mdbook-lini.css"]

Errors

A block that fails to compile becomes a visible <pre class="lini-error"> on the page and a message on stderr; the rest of the build carries on, so one bad diagram never costs you the book. Warnings — an unroutable link, say — only go to stderr. Diagnostics carry the chapter path and the real line number in the markdown file:

mdbook-lini: figures.md:41:1: warning: impossible (a -> b): no legal route

Links

Everything about the language itself lives with Lini, not here:

License

MIT