コンテンツにスキップ

PDFLang で PDF を分解する

画面の上では、PDF は平らな一枚に見えます。ページの写真のようなものです。実際は 違います。PDF は部品の詰まった箱で、ファイルについて人が尋ねることのほとんど — これはちゃんと印刷できるか、何か欠けていないか — は、実はその部品のどれかについての 質問です。

PDFLang は中に手を伸ばす方法を五つ渡してくれます。四つはスクリプトが始まった時点で すでにそこにあり、五つ目は自分で描きます。

どのスクリプトも doc という変数から始まります。開く手続きも読み込みも要りません。 doc が、いま検査されているファイルそのものです。

check "何を見ているのか" {
print("ファイル:", doc.filename)
print("ページ数:", doc.page_count)
print("タイトル:", doc.title)
}

doc.title と doc.author はファイルのメタデータから来ます。PDF が自分自身について 持っている情報で、ページ上には現れません。書き出した人が埋めていないことが多く、 空のままであることがよくあります。それ自体が検査の対象になります。

check "ファイルが自分の名前を持っている" {
assert doc.title != "", "PDF のメタデータにタイトルがありません"
}

doc は三つのリストも持っています。doc.pages、doc.fonts、doc.images です。 普通のリストなので、リストにできることはすべてできます。

doc.pages はページを一枚ずつ渡します。各ページは自分の寸法と、乗っているインキの 量と、どのボックスが定義されているかを知っています。

寸法の単位はポイントです。PDF が考える単位で、1 インチ = 72 ポイント。A4 なら 595 × 842 になります。ただしポイントで考える必要はありません。210mm や 8.5in と 書けば比較は成立します。

check "すべて A4 か" {
doc.pages.each { |page|
assert page.width > 200mm,
"#{page.number} ページが狭すぎます: #{page.width}pt"
}
}

page.number に注目してください。ページは会話と同じく 1 から数えます。ですから 失敗メッセージに出る番号は、人がそのまま開くページ番号です。

そしてボックスがあります。印刷用のページは、そのページをどう扱うかを示す矩形をいくつか 持っています。TrimBox は断裁機が切る位置、BleedBox はそれより少し大きい領域で、 断裁位置を越えて色を伸ばしておく場所です。1 ミリのずれで白い縁が出ないようにするため です。これらがないファイルは、画面上は完璧に見えても印刷所では使えません。

check "断裁の準備ができている" {
doc.pages.each { |page|
assert page.has_trim_box, "#{page.number} ページに TrimBox がありません"
assert page.has_bleed_box, "#{page.number} ページに BleedBox がありません"
}
}

ページは page.tac も報告します。そのページで最もインキが乗る場所の量を百分率で 表したものです。4 色すべてが全力なら 400% になりますが、それを受ける印刷機はあり ません。300% がよく使われる上限です。

doc.fonts はファイルが使っている書体を並べます。そして各書体は、ひとつの重要な問い に答えます。埋め込まれているか。

埋め込まれたフォントは PDF の中を一緒に旅します。埋め込まれていないフォントは名前が 書いてあるだけで、ファイルを開いた機械が手持ちの書体で代用します。文字が消えるわけ ではありません。形が変わるのです。行の折り返しが変わり、1 行に収まっていた見出しが 2 行になり、印刷されるまで誰も気づきません。

check "すべてのフォントがファイルと一緒に運ばれる" {
doc.fonts.each { |font|
assert font.is_embedded, "フォント '#{font.name}' が埋め込まれていません"
}
}

doc.images はファイル内のすべての画像を、page.images は特定のページの画像を 返します。理解しておく価値があるのは dpi です。

DPI は 1 インチあたりのドット数、つまり印刷 1 インチの中にどれだけの情報があるかです。 PDFLang は画像のメタデータに書かれた数値を報告しません。報告するのは実効解像度、 すなわち画像が実際に持つ画素数を、印刷される寸法で割った値です。1000 画素の写真を 20 センチに引き伸ばせば、それは低解像度の画像です。メタデータが何と言おうと関係あり ません。印刷が鮮明になるかどうかを決めるのはこの数字です。

check "印刷に耐える鮮明さ" {
doc.images.each { |img|
assert img.dpi >= 300,
"#{img.page_number} ページの画像: #{img.dpi} DPI、300 必要です"
}
}

もうひとつよく使われるのが img.color_space です。オフセット印刷は CMYK で動きます。 RGB のままの画像は変換が必要で、変換すると色がずれます。刷版を作る前に気づくほうが ましです。

ここまでの四つはファイルに付いてきます。五つ目は違います。region は自分で定義する 矩形で、ページ全体ではなく一部について問うためのものです。

footer = region(0, 0, 595, 60, "フッター")
band = region(20mm, 250mm, 60mm, 15mm, "警告帯")

覚えておくべきことがひとつあります。原点の位置です。PDF では 0, 0 はページの 左下の角であり、左上ではありません。Y は上に向かって増えます。

region があるから、仕事にとって本当に意味のある規則が書けます。「このファイルに法定 文言が入っているか」ではなく、「法定文言がそのために確保された帯の中にあるか」です。

check "警告帯" {
band = region(0, 700, 595, 142, "帯")
content = text::extract_from_region(1, band)
assert content.contains("要処方"),
"帯に必須の文言がありません"
}

region 同士は比較もできます。footer.intersects(header) は二つの領域が重なっているか を教えます。他の用途に確保した場所へ迷い込んだ要素を捕まえる方法です。 footer.expand(5mm) と footer.inset(3mm) は、余裕を持たせた、あるいは狭めた複製を 返します。公差のためです。

文書の型の章に、五つすべてのプロパティとメソッドが例つきで並んで います。参照用の資料です。通読するのではなく、必要になったときに引いてください。

五つが実際の仕事で一緒に使われている様子を見たいなら、実践レシピ とサンプルがその場所です。

DigitalOceanこのサイトのホスティングを提供してくださる DigitalOcean に感謝します。