惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
美团技术团队
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
月光博客
月光博客
J
Java Code Geeks
Jina AI
Jina AI
罗磊的独立博客
宝玉的分享
宝玉的分享
S
SegmentFault 最新的问题
D
DataBreaches.Net
博客园 - 叶小钗
腾讯CDC
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Last Week in AI
Last Week in AI
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Google DeepMind News
Google DeepMind News
阮一峰的网络日志
阮一峰的网络日志
B
Blog
V
Visual Studio Blog
雷峰网
雷峰网
博客园 - 【当耐特】
Apple Machine Learning Research
Apple Machine Learning Research
Engineering at Meta
Engineering at Meta
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
The Page Order Math Behind Saddle-Stitch Booklets Is Weir...
hiyoyo · 2026-04-23 · via DEV Community
Cover image for The Page Order Math Behind Saddle-Stitch Booklets Is Weirder Than You Think. So I Automated It. [Devlog #8]

hiyoyo

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
}

Enter fullscreen mode Exit fullscreen mode

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)
}

Enter fullscreen mode Exit fullscreen mode


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
}

Enter fullscreen mode Exit fullscreen mode

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