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 1t is a truth universally acknowledged, that a single man in possessionIof a good fortune, must be in want of a wife.However little known the feelings or views of such a man may be onhis first entering a neighbourhood, this truth is so well fixed in the mindsof the surrounding families, that he is considered the rightful property ofsome one or other of their daughters."My dear Mr. Bennet," said his lady to him one day, "have you heardthat Netherfield Park is let at last?"Mr. Bennet replied that he had not."But it is," returned she; "for Mrs. Long has just been here, and she toldme all about it."Mr. Bennet made no answer."Do you not want to know who has taken it?" cried his wife impa-tiently."You want to tell me, and I have no objection to hearing it."This was invitation enough."Why, my dear, you must know, Mrs. Long says that Netherfield istaken by a young man of large fortune from the north of England; thathe came down on Monday in a chaise and four to see the place, and wasso much delighted with it, that he agreed with Mr. Morris immediately;that he is to take possession before Michaelmas, and some of his servantsare to be in the house by the end of next week.""What is his name?""Bingley.""Is he married or single?""Oh! Single, my dear, to be sure! A single man of large fortune; four orfive thousand a year. What a fine thing for our girls!""How so? How can it affect them?"1
page 1

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.

Layout time and peak memory for two public-domain novels.
bookpagesto PDFpeak memory
Pride and Prejudice333287 ms12 MiB
The Count of Monte Cristo12541.10 s47 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, &registry);

let output = fleuron::layout::layout_book(&book, &styles, &registry);
let bytes = fleuron::pdf::write(&output, &registry, &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 pages
import { 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.