PDF間のリンク(2)

前回の続きでPDFのNamed Destinationからページ番号を得る方法を考えてみました.
 
例えば次のようなtopicがあったとします.
 
<topic id="topic_14F0A9302584C94D">
  <title>Topic title</title>
  ...
</topic>
 
通常DITA Open Toolkitの中間ファイルではtopicが必ずユニークなidを持つことを前提とせずに、独自に"unique_NN"というようなidを再割り当てしてしまいますが、それでもこのidは@oidという属性で残してくれてあります.これを利用して、Named DestinationとしてXSL-FOを生成しPDFに登録してやります.
 
<fo:block id="topic_14F0A9302584C94D" axf:destination-type="xyz-top">
  ...
</fo:block>
 
このFOから作成されたPDFを強引にテキストエディタで覗くと次のようなエントリーを見つけることができます.
 
1161 0 obj
<<
/Type /Catalog /Pages 1 0 R
/Outlines 1046 0 R/PageMode /UseOutlines
/PageLabels
<< /Nums [ 0 << /S /D >>
1 << /S /r >>
7 << /S /D >> ] >>
/Names 1162 0 R /Lang (EN) >>
endobj
 
これはカタログオブジェクトで、/Namesで示されたオブジェクトがNamed Destinationのエントリです.
 
1162 0 obj
<< /Dests 1163 0 R >>
endobj
 
1163 0 obj
<< /Kids [ 1164 0 R 1165 0 R 1166 0 R 1167 0 R 1168 0 R 1169 0 R 1170 0 R ]
>>
endobj
 
とたどってゆくと次のようなオブジェクトのエントリを見つけることができます.(すごく簡略化しています.)
 
1167 0 obj
<< /Limits [ (...) (...) ]
/Names [ (...) 699 0 R
...
(topic_14F0A9302584C94D) 12 0 R
...
>>
endobj
 
ここには、さっきのtopicのidが格納されています.この12 0 Rを調べると
 
12 0 obj
<< /D [ 10 0 R /XYZ null 720 null ]
>>
endobj
 
とNamed Destinationの定義があります.そしてこの 10 0 をたどると、
 
10 0 obj
<</Type /Page
/Trans << >>
/Parent 2 0 R /Resources <</Font <<
/F2 20 0 R/F3 21 0 R >>
/ProcSet [/PDF/Text/ImageC]
>>
 
ということでようやくこのtopicが位置するページオブジェクトにたどり着くことができました.これが目的のページです.
 
このページオブジェクトは
 
2 0 obj
<<
/Type /Pages
/Kids [ 3 0 R 10 0 R 23 0 R 40 0 R 50 0 R 56 0 R 61 0 R 66 0 R 70 0 R 86 0 R
...
713 0 R]
/Count 71
/Parent 1 0 R >>
endobj
 
というページ辞書を参照することで物理的に2ページ目であることがわかります.でもこのtopicのAcrobat Readerで表示されているページ番号は2ではなくiです.
 
イメージ 1
 
 
これは最初の1161 0オブジェクトでページラベルが
 
<< /Nums [ 0 << /S /D >>
1 << /S /r >>
7 << /S /D >> ] >>
 
すなわち、
 
0ページから D Decimal arabic numerals
1ページから r Lowercase roman numerals
7ページから R Uppercase roman numerals
 
としてあるからです.これにより2ページ目(0から数えれば1ページ目)はi,ii,iiiの形式、すなわち「i」がページ番号ということがようやくわかります.
 
つまり、Named Destinationの値がわかればPDFの内部構造を追うことにより、それが何ページかは取得できそうです.
 
これをどうやって組み込むかですが、DITA⇒XSL-FOのスタイルシートJavaXSLTプロセッサ(DITA-OTの場合はSaxon 9)から呼び出すJavaのライブラリ静的関数として実装すればよいでしょう.
 
たとえばこの例の場合だとPDFファイルのパスと"topic_14F0A9302584C94D"というNamed Destinationの値を渡して"i"という文字列を得るという感じになります.これで
 
For more detail, see "XXXX" on page NN in Technical guide.
 
のNNの部分は"i"に置き換えることができます.
 
しかしPDFからページ番号を得ることはできても、あまり実用的ではないかもしれません.なぜならPDFからはページ番号を得ることはできてもtopicのタイトル文字列を得ることは容易ではないからです.つまりリンク元のrelated-links(reltable)でも参照先のタイトル("XXXX")は手入力しなければなりません.
 
でもそこまでやってもページ数が得たいという場合にはこのようなライブラリでも作る意味はあるでしょう.
 
JavaのPDFライブラリはいろいろなものが出ているようですが、ApacheのPDFBoxが結構有名なようです.時間があったら久しぶりにJavaに挑戦してみようかなと考えています.でもPDFは結構大変なので簡単には行かないかもしれません.そんなに甘くはないのです.
(私の女房はPDF Reference 1.4の日本語版の本がほどけてボロボロになるまで読んでいますが、それでもまだPDFに悩まされ続けています.)