家庭用プリンターで中綴じ冊子を作るImposition(面付け)エンジンをRustで実装した【開発日誌 #8】
開発日誌 #8 です。前回はGhost Engineで常駐型デーモンアーキテクチャを作った話を書きました。
※検証環境は8年前のMacBook Airです。
今回は Publisher機能、特にImposition(面付け)エンジンの設計について書きます。
「普通のプリンターで本格的な中綴じ冊子を作る」ために、ページ順序の計算が意外と複雑でした。
Impositionとは
A4用紙に両面印刷して半分に折ると、4ページ分の冊子になります。
このとき、ページを印刷順に並べるのではなく、折ったときに正しい順序になるように並べ替える必要があります。
8ページの冊子を例にすると:
【冊子のページ順】 【印刷するときの並び】
1 2 3 4 5 6 7 8 → 表面: [8, 1] [2, 7]
裏面: [6, 3] [4, 5]
このページ並び替えのロジックを Imposition(面付け) と呼びます。
InDesignやAcrobatでは有料オプションですが、Hiyoko PDF VaultではRustで実装しました。
面付けのアルゴリズム
総ページ数を4の倍数に揃えた上で、各シートのページ番号を計算します。
pub fn compute_imposition(total_pages: u32) -> Vec<(u32, u32, u32, u32)> {
// 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;
// 裏面: [先頭側ページ+1, 最終側ページ-1]
let back_left = i + 2;
let back_right = padded - i - 1;
// (表左, 表右, 裏左, 裏右)
layout.push((front_left, front_right, back_left, back_right));
}
layout
}
このレイアウト情報をもとに、lopdfでページを並べ替えた新しい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 {
// 表面シートを作成(2ページを横に並べる)
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(自動目次)
もう一つのPublisher機能が Auto TOC(自動目次生成)です。
PDFの各ページの先頭行をヒューリスティックに解析して、見出しっぽいテキストを検出します。
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
}
検出した見出しリストをもとに、lopdfでPDFリンク付きの目次ページを先頭に挿入します。
ハマったところ
空白ページの扱い
ページ数が4の倍数でない場合、空白ページを末尾に追加する必要があります。
この空白ページのサイズを元PDFのページサイズに合わせないと、印刷時にズレが生じました。
見開きのマージン
中央で折る部分(ノド)にマージンを設けないと、文字が折り目に隠れます。
ページを並べるときに内側マージンを自動で追加する処理が必要でした。
現在の状況(dev版)

次回
次回は Sanctuary Viewer(痕跡ゼロ閲覧) の話を書きます。
PDFを開いた記録をmacOSのどこにも残さない設計です。
Hiyoko PDF Vault(日本語) → https://hiyokoko.gumroad.com/l/HiyokoPDFVault_jp
X → @hiyoyok
Discussion