All tests run on an 8-year-old MacBook Air.
Print pages 1, 2, 3, 4 in order, fold the paper — and the result is wrong.
Booklet printing requires a completely different page ordering. For an 8-page booklet, you print [8, 1] on the front of sheet 1, [2, 7] on the back. This is called imposition, and it's a paid feature in InDesign and Acrobat.
I built it in Rust.
The math
For a saddle-stitch booklet, total pages must be a multiple of 4 (blank pages pad the remainder). Each sheet carries 4 logical pages: front-left, front-right, back-left, back-right.
pub fn compute_imposition(total_pages: u32) -> Vec<(u32, u32, u32, u32)> {
// Round up to nearest multiple of 4
let padded = ((total_pages + 3) / 4) * 4;
let sheets = padded / 4;
let mut layout: Vec<(u32, u32, u32, u32)> = Vec::new();
for i in 0..sheets {
let front_right = i + 1;
let front_left = padded - i;
let back_left = i + 2;
let back_right = padded - i - 1;
// (front_left, front_right, back_left, back_right)
layout.push((front_left, front_right, back_left, back_right));
}
layout
}
That layout then drives a lopdf reordering pass to produce the print-ready PDF:
pub fn build_imposition_pdf(
original: &Document,
layout: &[(u32, u32, u32, u32)],
) -> Result {
let mut new_doc = Document::with_version("1.5");
for (front_left, front_right, back_left, back_right) in layout {
add_sheet(&mut new_doc, original, *front_left, *front_right)?;
add_sheet(&mut new_doc, original, *back_left, *back_right)?;
}
Ok(new_doc)
}
Auto TOC
The second Publisher feature: automatic table of contents generation.
Heuristic heading detection — short lines that don't end with sentence-ending punctuation get flagged as headings:
pub fn detect_headings(doc: &Document) -> Vec<(u32, String)> {
let mut headings: Vec<(u32, String)> = Vec::new();
for (page_num, _) in doc.get_pages() {
if let Ok(text) = doc.extract_text(&[page_num]) {
let first_line = text.lines().next().unwrap_or("").trim();
if first_line.len() > 2
&& first_line.len() < 60
&& !first_line.ends_with('。')
&& !first_line.ends_with('.')
{
headings.push((page_num, first_line.to_string()));
}
}
}
headings
}
The detected headings become a linked TOC page prepended to the PDF via lopdf.
Where I got stuck
Blank page sizing — when page count isn't a multiple of 4, blank pages fill the gap. They need to match the source PDF's page dimensions exactly or the print layout breaks.
Gutter margins — the fold point needs extra inner margin or text near the spine disappears into the crease. Required an automatic margin offset during the sheet assembly step.
Next devlog
Sanctuary Viewer — opening PDFs without leaving a trace anywhere on macOS. No recent files, no cache, no history. The file was never opened.
Hiyoko PDF Vault → https://hiyokoko.gumroad.com/l/HiyokoPDFVault
X → @hiyoyok
Top comments (0)