0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

家庭用プリンターで中綴じ冊子を作るImposition(面付け)エンジンをRustで実装した【開発日誌 #8】

0
Posted at

開発日誌 #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のページサイズに合わせないと、印刷時にズレが生じました。

見開きのマージン

中央で折る部分(ノド)にマージンを設けないと、文字が折り目に隠れます。
ページを並べるときに内側マージンを自動で追加する処理が必要でした。

次回

次回は Sanctuary Viewer(痕跡ゼロ閲覧) の話を書きます。
PDFを開いた記録をmacOSのどこにも残さない設計です。


Hiyoko PDF Vault(日本語) → https://hiyokoko.gumroad.com/l/HiyokoPDFVault_jp
X → @hiyoyok

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?