ぽんぽこ日記

プログラミング、読書、日々の生活

PDFの文書構造を取り出す

今回は、PDF文書の目次情報を取得する方法について記します。

アウトラインそのものはPDFカタログの「Outlines」から取得できます。このエントリは木構造になっているので、深さ優先で、「First」項目をスキャンし、木をスキャンし終わったら「Next」項目をスキャンしていけば目次の各項目を取得できます。

各項目を表すCGPDFDictionaryに「Title」キーが設定されていれば、目次の見出しを取得することができます。
また「A」キーにはページオブジェクトへの実体が格納されています。前回説明したフォントの設定や、コンテンツストリームなどを取得できます。
問題は、ページオブジェクトにはこのページが前から何番目のページなのか、すなわちページ番号についての情報は含まれていないことです。本の目次同様に、「Outlines」を使いたい場合、どうしてもページ情報とページ番号の対応が欲しいところです。

したがって、何か別の方法で、ページオブジェクトとページ番号の対応を解決する必要があります。

さて一方、文書に含まれるすべてのページ情報は、PDFカタログの「Pages」から取得できます。「Pages」も木構造になっているので、「Outlines」同様に深さ優先でスキャンすることで、文書の先頭からのページの並びを取得できます。

なので、Outlinesの情報とPagesの情報を一意に照合できれば、目的が果たせるはずなので、いろいろ試行錯誤して、一応の解決をみました。

(ここからは筆者の推測に基づいた実装なので、正しい動作かどうかは保証できません。各自の判断と検証の上お使いください。)

その結果、今回の記事のポイントとなる部分になるのですが、どうやら、「Outlines」から取得できるページオブジェクトへのポインタと、「Pages」から取得できるページオブジェクトは同一のものを指しているようです。そもそもPDFフォーマットは非常に細かい粒度でユニークな情報にはオブジェクトIDがふられていて、文書中の別の箇所から参照できるのですが、CoreGraphicsのPDFパーサライブラリは、そうした参照関係=オブジェクトグラフを、そのままメモリ上に展開しているようです。

そこで、この構造を前からスキャンして、得られたページオブジェクトに番号をふっておいて、「Outline」カタログから取得されたページオブジェクトを「Pages」から取得したものと照合して、一致するものが見つけることで、ページ番号を取得できます。


もう一つのパターンとして、Outlinesの各エントリーに「A」キーが無い場合、すなわちページオブジェクトへの参照が格納されていない場合があります。
この場合、「A」キーの代わりに、「Dest」というキーが設定されており、このキーに格納された文字列がページへの間接参照になっています。
Dest文字列からページの実体データを引くには、「Names」カタログの対応表を調べます。
Names/Dests/Kidsをたどると、「Names」というキーで参照できる配列が得られます。0番目に間接参照の文字列、1番目にページオブジェクトが格納されています。

サンプルコードをGitHubにあげました。再帰呼び出しを使っていたり、メモリの解放処理が不十分だったりするので、目的に合わせて適当に修正して使ってください。

https://github.com/ponpoko1968/PDFStructure

追記:↓こういう本が出るようです。期待。

PDF構造解説

PDF構造解説

Amazon