CSS paged media, in Rust
A typesetting engine
for novels.
fleuron turns a manuscript into a typeset book and writes it out as a print-ready PDF. Styling is CSS. It is built for markdown writing tools.
Pride and Prejudice, 333 pages, becomes a PDF in 287 ms. The book below is being set by that same engine, in this browser, right now.
Chapter 1 of Pride and Prejudice. Change the manuscript or the stylesheet and the book is laid out again. The preview and the PDF are painted from the same display list, so they cannot come out different.
Paged media
A built-in stylesheet sets the defaults and yours overrides whatever you want to change. fleuron understands a subset of CSS, and reports what it does not.
@page {
size: 5.5in 8.5in;
margin: 0.8in 0.7in;
@bottom-center {
content: counter(page);
}
}
@page :left {
@top-left {
content: "Pride and Prejudice";
}
}
section {
break-before: recto;
}
p {
orphans: 2;
widows: 2;
}Performance
Two complete novels, measured from markdown to PDF.
| book | pages | to PDF | peak memory |
|---|---|---|---|
| Pride and Prejudice | 333 | 287 ms | 12 MiB |
| The Count of Monte Cristo | 1254 | 1.10 s | 47 MiB |
Apple M-series. Run it on yours: the same two books, laid out in your browser, with the machine named beside the numbers.
Using it
fleuron is the engine, pure Rust with no I/O.fleuron-markdown reads a manuscript into the content tree it lays out. fleuron-cli takes a manuscript and stylesheets and writes a PDF. fleuron-wasm runs the same pipeline in a worker, and is what set the book above.
let markdown = std::fs::read_to_string("manuscript.md")?;
let (sections, warnings) =
to_sections(&markdown, "manuscript.md", &Options::default());
let book = assemble(frontmatter(&markdown), sections);
let css = std::fs::read_to_string("book.css")?;
let mut registry = bundled_registry()?;
let mut sheets = Stylesheets::parse(&[Source::author("book.css", &css)]);
sheets.load_fonts(&mut registry, &Files(PathBuf::from(".")));
let styles = sheets.compile(&book, ®istry);
let output = fleuron::layout::layout_book(&book, &styles, ®istry);
let bytes = fleuron::pdf::write(&output, ®istry, &book.metadata)?;
std::fs::write("book.pdf", bytes)?;cargo install --git https://github.com/zachhannum/fleuron fleuron-cli
fleuron manuscript.md -o book.pdf -c book.css
# fleuron: manuscript.md → book.pdf: 333 pagesimport { Preview } from '@fleuron/wasm';
// Starts the worker, loads the module, keeps the session.
const preview = await Preview.mount(document.querySelector('#book'));
await preview.setStyle(css);
await preview.setMarkdown(markdown);
preview.page = 12;
const pdf = await preview.exportPdf();fleuron is pre-alpha. It is being built as the pagination backend for Orca, an Obsidian plugin for writing novels.