マクロツイーター

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

実験レポート:文書情報が文字化けしない hyperref の設定(2)

前回の続き〉
8 ビット欧文 TeX + CJK パッケージの場合

この場合、encoding の値を pdfdocunicodeauto の何れにしても、非 ASCII 文字の直接入力(つまり (4))について LaTeX がエラーを出して通らない。

hyperref の説明書を見ると、CJK パッケージをサポートするためのオプションとして CJKbookmarks というものがあることが判る。

\usepackage[CJKbookmarks]{hyperref}

これを指定した時の、test.out の「結果の文字列」は次のようになる。

  • (1) (!A B-C)
  • (2) (\241A B\204\307)
  • (3) (\241A B\204\307)
  • (4) (\241あ B\204\307) [UTF-8]

つまり、CJK パッケージのない「8 ビット欧文 TeX/pdfencoding=pdfdoc」と同じになる。((つまり、CJK パッケージ読込時について、通常は非 ASCII 文字が入力されると CJK パッケージの実装コードが働き、それが hyperref の処理と衝突してエラーになるが、CJKbookmarks 指定時は、CJK 非読込時と同様に当該の文字を表すバイト列が「そのまま」出力されるようになる。今の例だと UTF-8 となるが、例えば入力文字コードEUC-JP にしていた場合は「そのまま」EUC-JP のバイト列が出力される。これは pTeX 系の出力結果とも類似している。))これで確かに LaTeX のエラーは出なくなったが、しかし CJK パッケージのない場合と同じということは、Reader で閲覧した時に表示される文字列は全く同じように化けてしまう(「¡Aㆇ B—Ç」となる)ことになる。つまり、この CJKbookmarks だけでは問題は解決していない。

それでは、CJK パッケージが使われることの多い中国ではどのようにして正しい「しおり」を生成しているのだろうか。気になったので調べてみた。

  • CJKbookmarks を指定した時の出力は、文字命令(LICR;\textexclamdown)の部分を除けば、「中国語の文字コード(GBK や UTF-8)で文字列がそのまま出力されている」と見做せる。これはちょうど日本で pTeX 系を用いた時と同じ状況になっている。
  • 中国では(日本と同じく)文字命令がほとんど使われない。*1
  • 従って、pTeX 系について日本人が用いてきたのと同じ解決法が使えることになる。そして実際に同じ解決法が使われている。
  • GBK について、日本の out2uni と同様の処理をする gbk2uni というフィルタがあり、pdfTeX を使う場合はこれが使われているらしい。
  • そして PDF への変換に dvipdfmx を使う場合は*2 GBK-EUC-UCS2 という ToUnicode CMap ファイルを用意して、pdf:tounicode special でそれを指定するという方法が使われている。

入力文字コードに GBK でなく UTF-8 を使う場合も上記の解決法は適用できる(実際に行われているかは知らない)。しかし、UTF-8 の入力の場合はもっと便利な解決法が開発されている。それは CJKutf8 パッケージを使用することである。TeX Wiki の CJK パッケージの記事で紹介されているように、CJKutf8 の主な役割は CJK の UTF-8 による直接入力と inputenc(の utf8 エンコーディング)による欧文の UTF-8 入力を共存させることであるが、同時にこのパッケージは hyperref の文書情報の出力に対する完全な解決を与えている。具体的には、プレアンブルで次のように指定する。

\usepackage[unicode]{hyperref} % pdfencoding=unicode でも同じ
\usepackage{CJKutf8} % 内部で CJK と inputenc が読まれる

これで (4) の出力結果が次のようになる。

  • (4) (\376\377\000\241\060\102\000\040\000B\040\024\000\307\001\112)

入力中の〈あ〉が正しく UTF-16BE (\060\102) に変換されていることが判る。しかも Unicode での変換なので LICR の部分も〈Ŋ〉の文字も問題なく処理される。従って、ここから生成される PDF のしおりの文字列は完璧な「¡あB—ÇŊ」となる。

(u)pTeX/pdfencoding=pdfdoc

日本語文字を含む (4) 以外は当然ながら「8 ビット欧文 TeX/pdfencoding=pdfdoc」と同じ結果になる。(4) については、日本語文字がそのまま出力される。(〈Ŋ〉は欠落する。)

  • (4) (\241あ B\204\307)

ただしこれの文字コードが何であるかについては複雑である。仮に、UTF-8 で書かれた test.texWindows 上の「platex -kanji=utf8」コマンドでコンパイルしたとする。この場合、まず test.out に書き出された〈あ〉は出力用文字コード((これは通常は入力用の文字コードと同じであるが、入力文字コード自動判定が有効の場合(W32TeXpTeX は既定で有効になっている)は異なる場合がある。例えば、-kanji=sjisWindows ではこれが既定)としつつ UTF-8 のファイルを(自動判定で)読み込んだ場合は、入力は UTF-8 であるが出力はシフト JIS(-kanji の値)となる。))の UTF-8(つまり〈E3 81 82〉)となる。これまでの説明で、これが PDF に書き出されると言ったが、これは実は不正確であり、hyperref でしおりを生成する場合、2 度目の LaTeX の実行で、.out ファイルから読んだリテラル文字列を special 命令として(そのまま)DVI に書き出しており、本当に PDF に書き出されるのは DVI 中に記された文字列である。といっても「そのまま」書き出しているだけなので普通は .out 中のバイト列と DVI 中のそれは完全に等しいのであるが、pTeX 系の場合はここで(エンジンの処理として)文字コードの変換が起こる。今の場合、DVI 中の文字列はエンジンの内部漢字コード(WindowspTeX の場合はシフト JIS)で表されていて、〈あ〉は〈82 A0〉となる。

従って、その DVI を dvipdfmx で PDF に変換すると、しおりに表示される文字列は、

〈A1 82 A0 20 42 84 C7〉

というバイト列を PdfDocEncoding で解釈して得られる

¡‡€ B—Ç

ということになる。つまり、何も対策をせずに dvipdfmx で変換すると、和文文字は化けてしまう。

文字化けを防ぐために一般的に使われるのが、dvipdfmx の pdf:tounicode special である。内部漢字コードがシフト JIS である場合、DVI 中に以下のような special 命令を含ませておく。

pdf:tounicode 90ms-RKSJ-UCS2

そうすると、dvipdfmx は DVI 中に書かれた PDF 文字列をシフト JIS で解釈し、UTF-16BE に変換した上で PDF に書き出すようになる。だから仮に(元の TeX の)入力が〈あ〉だけだったとすると、DVI 中のバイト列は〈82 A0〉となり、pdf:tounicode が有効になっていれば、PDF のしおりとして表示される文字列は正常に「あ」になるわけである。((PDF 文書中には実際には <FEFF3042> という 16 進文字列リテラルが書き出されている。))しかし、test.tex の (4) のように入力文字列が文字命令(LICR)を含んでいる場合にはこれでは上手くいかない。何故なら、この場合〈A1 82 A0 20 42 84 C7〉というバイト列をシフト JIS で解釈することになるが、そうすると、本来 PdfDocEncoding で解釈すべき〈A1〉(→〈¡〉)までシフト JIS で解釈されてしまい、結果は半角カナの〈。〉に化けてしまう。*3pTeX 系では文字命令が使われる機会が少ないからあまり問題にならないが、本当は pdf:tounicode special だけでは完全な解決にはならないのである。

なお、upTeX についてもこの節で述べた話が通用するが、その場合は仮に「内部内部漢字コードが UTF-8 である」と見做せばよい。*4

(u)pTeX/pdfencoding=unicode

(4) 以外は「8 ビット欧文 TeX/pdfencoding=unicode」と同じ結果になる。(4) は次のようになる

  • (4) (\376\377\000\241\000あ\000\040\000B\040\024\000\307\001\112)

UTF-16BE のバイト列の表現のなかに(DVI 中では)内部漢字コードの和文文字が紛れ込んだ形になってしまう。内部漢字コードがシフト JIS だとすると、1 バイトとして処理されたところが実際には 2 バイト(82 A0)ある状態になるので、高位と低位の対応がずれて、それ以降の表示が全部化けることになる。

(本来の結果)〈00A1 3042 0020 0042 2014 00C7 014A〉
(実際の結果)〈00A1 0082 A000 2000 4220 1400 C701 4A00〉

pdf:tounicode special を用いた(部分的な)解決法は、pdfencoding=pdfdoc の時の出力を前提とする。現状では、(u)pTeX 系で pdfencoding=unicode を使う場面は存在しない。

(u)pTeX/pdfencoding=auto

これは和文文字を含む (4) について LaTeX でエラーが発生する。

pTeX + pxjahyper パッケージ/pdfencoding=pdfdoc

pxjahyper パッケージ読込時は、自動的に正しい内部漢字コードを指定した pdf:tounicode special が出力される。そしてリテラルは次のようになる。和文(非 ASCII)文字の文字コードは内部漢字コードである。*5

  • (1) (!A B-C)
    → 「!A B-C」
  • (2) (A B―C)
    → 「A B―C」
  • (3) (A B―C)
    → 「A B―C」
  • (4) (あ B―C)
    → 「あ B―C」

この場合は内部漢字コードで文字列全体を書き出すという動作になる。正しい pdf:tounicode special が指定されているので書き出した「文字列」自体が正しく表示される。ところが、内部漢字コード(シフト JIS が EUC-JP)で表せない、すなわち「JIS X 0208 + ASCII」に含まれない文字は落ちてしまう。(変換の際に警告が出る。)例えば、〈¡〉や〈Ŋ〉は欠落し、〈Ç〉は〈C〉に置換される。しかし、JIS X 0208 の範囲の文字であれば、文字命令での入力であっても正しく処理される。例えば \textemdash(U+2014;JIS 1 区 29 点)は表示されている。*6

upTeX + pxjahyper パッケージ/pdfencoding=pdfdoc
  • (1) (!A B-C)
    → 「!A B-C」
  • (2) (¡A B—Ç) [UTF-8]
    → 「¡A B—Ç」
  • (3) (¡A B—ÇŊ) [UTF-8]
    → 「¡A B—ÇŊ」
  • (4) (¡あ B—ÇŊ) [UTF-8]
    → 「¡あ B—ÇŊ」

基本的に pTeX の場合と同じだが、「内部漢字コード」が UTF-8 となるため、全ての Unicode 文字が取り扱えるようになる。従って、しおりに表示される文字列が完璧なものになる。

(u)pTeX + pxjahyper パッケージ/pdfencoding=unicode または auto

pxjahyper パッケージは pdfencoding=unicode(UTF-16BE での出力)には対応していない。だから、「unicode には対応していない」という LaTeX のエラーが発生する。

*1:これは inputenc パッケージを使わない(使えない)からである。

*2:CJK パッケージは pdfTeX に対応しているので少し意外であるが、dvipdfmx は中国でもよく使われている。恐らく TrueType/OpenType フォントへの対応が dvipdfmx の方が進んでいたからだと思われる。

*3:同様に〈84 C7〉もシフト JIS で解釈されるが、その結果は JIS X 0208 の 8 区 41 点に相当し、この位置は未定義である。つまり全体の表示は「。あ B」(恐らく末尾に .notdef のグリフが出る)となる。

*4:実際には upTeX の内部では Unicode 符号値を UTF-8 形式でなく符号値そのもの(スカラー値)で取り扱っているのだが、少なくとも DVI に文字列を書き出す時には UTF-8 が用いられる。

*5:DVI の中の文字列での話。前述の通り、.out ファイルの中では出力漢字コードが使われる。

*6:ただし Unicode を JIS 系コードに変換する際に U+2014〈—〉 と U+2015〈―〉が同一視されて後者に寄せられる。このため実際に表示されるのは U+2015 となっている。