マクロツイーター

はてダから移行した記事の表示が崩れてますが、そのうちに直せればいいのに(えっ)

(x)dvipdfmx が文書情報を Unicode する件に関するメモ(3)

前回に続いてメモメモ。

前回の話の括りを受けて、「何が変換対象であるか」を具体的に調べてみた。

※調査対象は TeX Live 最新の dvipdfmx/xdvipdfmx

どの文字列を UTF-16 するのか

自分がソースコードを読んで理解したところによると、以下のようになっているようである。

ToUnicode 無しの dvipdfmx の場合

文字コードの変換は起こらず、実際に記述したバイト列がそのまま使われる。

ToUnicode 有りの xdvipdfmx の場合

以下に挙げる special 命令の引数に現れる文字列(バイト列)の一部について、UTF-8→BOM付UTF-16BE の変換が行われる。(これにより、PDF で文字列が正常に読み取られる。)

  • pdf:annot
  • pdf:bann
  • pdf:outline
  • pdf:article
  • pdf:bead
  • pdf:docinfo
  • pdf:docview

そして、変換の対象となる文字列は以下の規則で決まる。

  • これらの命令は何れも辞書型(<<...>>)の値の引数を 1 つ持っている。この辞書について以下の規則を適用する。
    • ①何らかのキーに対する値が文字列型の場合、その文字列は変換対象となる。
    • ②何らかのキーに対する値が辞書型の場合、その辞書に対して、①・②の規則を再帰的に適用する。
    • ③上記①・②により変換対象となる文字列以外は対象にならない。

ただし文字列の値が以下に該当する場合は例外となり変換されない。

  • 最初から BOM付UTF-16BE の有効なバイト列である場合。
  • ASCII 文字(0x7f 以下のバイト)のみからなる場合。PDFDocEncoding は ASCII の上位互換なので、この場合文字列は PDFDocEncoding として正常に読み取られる。

例えば、以下のような pdf:docview special を記述したとする。

[test.tex]
% plain TeX 文書, 文字コードUTF-8
%\special{pdf:tounicode UTF8-UTF16}% (*)
\special{pdf:docview <<
  /Snowman (☃)
  /Author (☃)
  /Keywords [(☃) (☃)]
  /SnowmanArray [ (☃) (☃) ]
  /SnowmanDict << /S (☃) /T (☃) >>
  /UTF16 <feff2603> % 最初からUTF-16
  /ASCII (Snow) % ASCIIのみ
>>}
\nopagenumbers \null \bye

この場合、実際に出力される Catalog 辞書の内容は以下のようになる。

1 0 obj
<<%※整形済
  /Snowman <feff2603> % UTF-16に変換
  /Author <feff2603>
  /Keywords [ <e29883> <e29883> ] % UTF-8のまま
  /SnowmanArray [ <e29883> <e29883> ]
  /SnowmanDict << /S <feff2603> /T <feff2603> >>
  /UTF16 <feff2603> % そのまま
  /ASCII (Snow) % そのまま
  /Pages 6 0 R
  /Type /Catalog
>>
endobj

上記の結果を踏まえて考えると、前回の話の中で「ページ番号を☃にできなかった」ことの理由は判る。

% 文字コードUTF-8
% PDFのページ番号表示を設定する
\special{pdf:docview << /PageLabels << /Nums [ 
    0 << /P (☃) >>          % UTF-8  → NG
] >> >>}

そう、(☃) があるのは、/Nums キーに対する配列の中だったのである。先の規則では、辞書の中にある辞書については再帰的に規則適用を行うが、配列は対象になっていない。従って、pdf:docview の引数の辞書の中にある文字列であるにも関わらず適切な変換が行われなかったわけである。

ToUnicode 有りの dvipdfmx/xdvipdfmx の場合

pdf:tounicode special により ToUnicode の指定が行われている場合は、dvipdfmx と xdvipdfmx の挙動は全く同じになる。そしてその場合の規則は、先の節で述べた「ToUnicode 無しの xdvipdfmx」の規則を少しだけ変えたものになっている。具体的には、①の部分を次のように変える。

  • 以下に示す名前のキーに対する値が文字列型の場合、その文字列は変換対象となる。
      /Title /Author /Subject /Keywords /Creator
      /Producer /Contents /Subj /TU /T /TM

そもそも「文字コードを変換する」必要がある理由は、PDF の仕様でテキスト文字列に使用する文字コードが固定されている(UTF-16BE か PDFDocEncoding)からである。ところが実は、special 命令の引数に与えられた辞書に含まれる「PDF の文字列型の値」が何でもテキスト文字列、すなわち「文字の列」を表すわけではなく、バイナリデータを表す場合もある。

従って、「ある特定の辞書型の引数」に関して、その中のすべての文字列を「一律に変換対象に入れる・入れない」という規則にするのでは、PDF の仕様に完全には対応できない可能性があるだろう。そこで dvipdfmx では「キー名も考慮に入れる」ことで、もう少し厳格に判定を行っているわけである。*1

例えば、先掲の test.tex で (*) 行の pdf:tounicode を有効にすると、結果は以下のように変わる(☆の部分が異なる)。

1 0 obj
<<%※整形済
  /Snowman <e29883> %☆
  /Author <feff2603>
  /Keywords [ <e29883> <e29883> ]
  /SnowmanArray [ <e29883> <e29883> ]
  /SnowmanDict << /S <e29883> /T <feff2603> >>  %☆ /Sはそのまま
  /UTF16 <feff2603>
  /ASCII (Snow)
  /Pages 6 0 R
  /Type /Catalog
>>
endobj

*1:ただそれにしても、本来なら「pdf:docview の引数の辞書の、/Hoge キーの辞書の、/Fuga キーの文字列ならば」と判定すべきところを「pdf:docview 等の幾つかの命令の引数の辞書のどこかの階層にある /Fuga キーの文字列ならば」で済ませているので完全に厳密ではない。