マクロツイーター

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

hyperref で日本語しおりに endash を出す件について

アレの話なんだが。

"--"が hyperref で正しく扱われず,しおりが変になる

以下のように \texorpdfstring{texstring}{pdfstring} を使って pdfstring の方を8進数表記で書くことで回避可能です.

\section{\texorpdfstring{a--b}{a\055\055b}}

例えば、次のような文書をコンパイル*1する。

\documentclass{jsarticle} % pLaTeX を使う場合
%\documentclass[uplatex]{jsarticle} % upLaTeX を使う場合
\usepackage[dvipdfmx,bookmarks=true,bookmarksnumbered=true]{hyperref}
\begin{document}
\section{enダッシュ--?\textendash!}
\end{document}

すると、DVI ファイルに記された節のしおりの文字列(これは最終的に PDF の文字列リテラルとして解釈される)は次のようになる。((「1」は節番号である。))

1 enダッシュ\205?\205!

この中の和文文字は「内部漢字コード((エンジンの内部で和文文字を表すのに用いられる漢字コードのこと。ソースファイルの漢字コードとは異なる場合がある。-kanji-internal で指定できるが、普通はシステムの既定のものから変更されない。))」(platex なら SJISEUC、uplatex なら UTF-8 *2)で符号化されている。「\205」は PDF の文法で符号値が 8 進数で 205(10 進数で 133)の文字を表す。PDF 中の文字列は既定では PDFDocEncoding という文字コードで解釈されることになっていて、その符号値 '205 の文字は endash(U+2013)である。

すなわち、(unicode オプション非指定時の*3)hyperref は、\textendash のような「文字命令」(LICR)を適切に PDFDocEncoding の(エスケープを用いた)PDF 文字列に変換する機能を有している。また hyperref は自前でリガチャ(-- → endash)を処理するが、この時も一旦「文字命令」(\textendash)を経由して PDFDocEncoding の文字列に変換している。従って、この文書の場合、endash は正しく変換されたことになる。しかし、「PDF 文字列は PDFDocEncoding で解釈される」ということは、和文文字(「内部漢字コード」で符号化されている)は正しく解釈されず文字化けが起こることになる。これは、「何も対策をしないと PDF のしおりの和文文字が化ける」というお馴染みの現象である。和文部分を正しく解釈させる方法は、DVI ドライバ毎に異なるが、dvipdfmx の場合、次のように「tounicode special」を出力すればよいことも知られている。((この special は当然ながら hyperref の出力する special(ここに和文文字列が含まれる可能性がある)より DVI 中で前に置く必要がある。最近の hyperref は \AtBeginShipoutFirst(atbegshi パッケージ)を用いていてこれは LaTeX 標準の \AtBeginDvi による出力よりも先行される。従って、一般的に hyperref 読込前に \AtBeginShipoutFirst で出力する方法が行われている。))

(内部漢字コードによって使い分ける)
pdf:tounicode EUC-UCS2          ← EUCの場合
pdf:tounicode 90ms-RKSJ-UCS2    ← SJISの場合
pdf:tounicode UTF8-UCS2         ← UTF-8の場合

これで、8 進エスケープ(\205 等)以外の部分は指定の漢字コードで解釈されるので、例えば内部漢字コードが EUC で、EUC で「enダッシュ」と書かれた場合は、“pdf:tounicode EUC-UCS2”を予め指定しておくと、「enダッシュ」いう文字列と解釈される。

さて、問題なのは、8 進エスケープの部分である。TeX Forum の投稿で述べられている解説に従うと、dvipdfmx は tounicode が指定されている場合は、8 進エスケープの部分も「指定した文字コードで符号化されたバイト列」であると思って解釈するようである。つまり「\205?\205!」は〈85 3F 85 21〉であり、これを内部漢字コードで解釈しようとする。この結果は当然 endash にならない(そもそも endash は JIS X 0208 にない)。普通は文字列は単一の方式で符号化されていて、数値でのエスケープは直接バイトを置くことの代替と考えるなら、この dvipdfmx の挙動は理に適っているだろう。一方で、hyperref は 8 進エスケープを PdfDocEncoding であると想定している*4ので、この仕様の間で齟齬が起こっていることになる。

私は「文字命令」(LICR)は常に正しい文字を表すべきであると考えているので、この状況は対処が必要であると捉えている。以下のような方法を用いると tounicode 指定時に全体が正しく解釈される文字列を DVI に出力できる。

(ASCII 以外の文字について)「文字命令」を「8 進エスケープ」に変換する代わりにその文字自体の和文文字トークンに変換する

例えば、\textsection であれば、これまでは \247 に変換していたところを和文文字の〈§〉に変換する。tounicode が効いていることを前提にすると、dvipdfmx はこれを U+00A7 と解釈する*5ことになる。

というわけで作ってみた。

基本的な使い方は、hyperref の後に pxjahyper パッケージを読み込むだけである。これで、内部漢字コードに応じて適切な tounicode special が出力されるし、また PDF 文字列中で「文字命令」が正常に機能する。

% upLaTeX 文書
\documentclass[uplatex]{jsarticle}
\usepackage[dvipdfmx,bookmarks=true,bookmarksnumbered=true]{hyperref}
\usepackage{pxjahyper}
\begin{document}
\section{enダッシュ--?\textendash!}
\end{document}

この文書を uplatex 2 回 → dvipdfmx で変換して、次のように正しいしおりをもつ PDF 文書ができる。

なお、endash(U+2013)は JIS X 0208 にないため、pLaTeX ではそもそも PDF 文字列に出力できない。*6通常はそういう場合はその文字命令を無効(hyperref で警告が出る)としているが、例外的に endash のようにリガチャで使われる文字については(hyperref の処理を通すために)代替的な定義を与えている。endash は「--」に変換される。

% pLaTeX 文書
\documentclass{jsarticle}
\usepackage[dvipdfmx,bookmarks=true,bookmarksnumbered=true]{hyperref}
\usepackage{pxjahyper}
\begin{document}
\section{enダッシュ--?\textendash!}
\end{document}

なお、以上の 2 つの例で文書ファイルの漢字コードは設定(自動判定や -kanji オプション)と合致する限り何でもよい。

*1:(u)platex を 2 回実行する必要がある。

*2:実際は、upTeX では和文文字は Unicode の符号値そのものを用いて処理されていて、UTF-8 表現を用いているのではない。ただ、DVI に special を書き出す場合には UTF-8 が使われるようなので便宜的にその扱いとする。

*3:pTeX 系を用いる場合は unicode オプションは役に立たないので、以降はこれを前提にする。

*4:というか、本来は全ての文字列を PdfDocEncoding として出力しているのであって、pTeX 系の和文文字は「考慮外」だから正しく扱えていないだけである、ともいえる。

*5:この時点では単なる「Unicode 文字列」を扱っているので、「和文」とか「全角」とかいう属性はもはや存在しない。

*6:out2uni を用いると JIS にない文字を pLaTeX でしおりに含められる。後の機会に説明する。