Hatena::ブログ(Diary)

マクロツイーター このページをアンテナに追加 RSSフィード Twitter

2013-02-22

TikZ は dvipdfmx をどこまでサポートするか? (1)

TeX Wiki「TikZ」の記事に述べられているように、TikZ の描画処理は間に PGF という「中間処理層」(「描画エンジン」と呼ばれている)を挟んで行われている。グラフィクスの描画には DVI ウェアの「拡張機能」を必要とするのであるが、DVI ウェア毎の仕様の差は全て PGF のドライバのレベルで吸収されている。つまり TIkZ 自体は「直接は DVI ウェア依存の動作」をせず(つまりドライバを持たず)、常に同じ「PGF の命令」を利用しているのである。すなわち、「TikZ は dvipdfmx をサポートするか」という問題は結局「PGF は dvipdfmx をサポートするか」という問題に帰着される。(なお、PGF は Beamer の描画処理でも用いられていて(TikZ を使っているのではない)、やはり Beamer も「直接は」DVI ウェア依存の処理を(原則的には)行っていないことは以前の記事で述べた。)

「dvipdfm」はどこまでサポートされるか?

現状の PGF/TikZ のマニュアルを見ると、そこには「dvipdfmx」のドライバに関する記述はないが、「dvipdfm」のドライバ(pgfsys-dvipdfm.def)に関する記述が載っている。(「Input and Output Formats」の節。)要点としては、次のような制限事項があると書かれている:

  1. LaTeX 上で動作する場合、画像挿入は graphicx の機能を利用する。透明マスク*1は非サポート。
  2. plain 上の動作では、画像挿入は非サポート。
  3. 図画間結合(inter-picture connections)は pdfTeX の最近の版でのみサポートされる。
  4. パターン*2は非サポート。
  5. 関数シェーディング*3は非サポート。

しかし、この記述は飽くまで dvipdfm についてのものである。dvipdfmx については状況は異なる。

前提知識: dvipdfm と dvipdfmx

LaTeX で dvipdfmx の使用を前提とする解説文書をみると、ものによって(同じパッケージであっても)ドライバ指定を「dvipdfm」としているものと「dvipdfmx」としているものがある。学習者の混乱の原因となっていると思うので詳しく説明しておく。

元々 dvipdfm というソフトウェアがあって、それを機能拡張したものが dvipdfmx である。ただし完全な上位互換ではないので、少し昔の TeX システムでは両方のソフトウェアが並存していた。基本的に別のものと考えるなら、ドライバ指定については「dvipdfm を使うなら dvipdfm、dvipdfmx を使うなら dvipdfmx」という自明な規則に従えばよい。

しかし、パッケージによっては、「dvipdfmx のドライバは提供されていないが、dvipdfm のものは提供されている」場合が存在する。そういうパッケージを dvipdfmx で使いたい場合には、応急処置として「dvipdfm」ドライバを使うことが考えられる。それで取りあえず dvipdfm の範囲の機能は使えることが期待できる。すなわち、ドライバ依存のパッケージを dvipdfmx で使う場合は、「dvipdfmx のドライバがあればそれを指定し、無ければ dvipdfm を指定する」という規則に従えばよい。(なお、dvipdfmx 用のドライバが提供されているかは当該パッケージのマニュアルを参照して確認するのが確実である。もちろん、dvipdfmx と dvipdfm のどちらのドライバも無い場合は、dvipdfmx での使用は諦めるしかない。)

少し昔の時点では、dvipdfmx のサポート状況は余り良くなかった。dvipdfmx を主に使っているのは CJK 言語の国であるという事情があり、欧米では少し前まで dvipdfmx はほとんど知られていなかったためである。(dvipdfm も余り使われず、専ら dvips か pdftex が使われている。)しかし、XeTeX がその PDF 生成のために「dvipdfmx の更なる拡張版」(xdvipdfmx)を使っていることが契機となり、dvipdfmx の存在が広く知られるようになり、それに伴って、ドライバ依存のパッケージで dvipdfmx 用のドライバをもつものが増えていった。そして現在は、(ソフトウェアの)dvipdfm は obsolete となり代わりに dvipdfmx を使うことが推奨されるようになっている。実際、TeX Live でも W32TeX でも本物の dvipdfm の実行ファイルはもはや存在せず、dvipdfm というコマンドを実行すると、代わりに dvipdfmx が「互換モード」で起動するようになっている。*4

「dvipdfmx」はどこまでサポートされるか?

さて、話を PGF に戻す。マニュアルには記載されていないが、現在の PGF では dvipdfmx 用のドライバ(ファイル名は pgfsys-dvipdfmx.def)が用意されている。*5先に述べた dvipdfm ドライバ制限事項と対照させると以下のようになる」。

  1. graphicx の機能を用いている点は変わらない。*6透明マスクはサポートされている。
  2. これについては後述。
  3. これは同じ。ただこれは本当は DVI ドライバではなく TeX エンジンに関する依存事項であることに注意。*7この機能については跡で詳しく扱う。
  4. パターンはサポートされている。
  5. 関数シェーディングはサポートされている。
TikZ のドライバを指定する「まともな」方法

ドライバ依存」のパッケージで「ドライバを指定する」ための方法について、大抵は「パッケージオプション\usepackage 命令のオプション)に書く」という方式が採られている。

\usepackage[dvipdfmx]{graphicx,color} % ドライバを dvipdfmx にする

ところが、理由は不明であるが、TikZ(および PGF)はこの「伝統的な」方式を使っていない。そして、マニュアルには次のような「よく解らない」説明のみが書いてある。*8

For the LaTeX format a sophisticated mechanism exists inside the graphics package and PGF plugs into this mechanism.

よく解らないわけだが、実装としては、PGF では(設定ファイル(pgf.cfg)で指定されない場合)graphics パッケージ(または graphicx パッケージ*9)のドライバ指定に従うということである。これに単純に従うと、例えば dvipdfmx ドライバを指定する場合は、「前もって graphicx をそのオプションで読み込んでおく」べき、ということになる。

\documentclass[a4paper]{article}
\usepackage[dvipdfmx]{graphicx}
\usepackage{tikz}
[追記] 上記の読込の方法では、color パッケージのドライバ指定が不正になることがあるので、よい子の皆さんは、マネをしないように。

しかし、この方式は如何にも奇妙である。恐らく、作者は「ドライバ指定は通常はグローバルオプション*10で指定する」という習慣を念頭においているのだと私は推測している。*11

\documentclass[a4paper,dvipdfmx]{article} % ドライバをグローバルオプションで指定
\usepackage{pict2e}   % 'dvipdfmx' オプションをもつ全てのパッケージで
\usepackage{animate}  % それが有効になる。
\usepackgage[bookmarks=true]{hyperref}

PGF は内部で(常に)graphicx を読み込んでいて、そのドライバ識別して既定のドライバ設定を決定している。従って、グローバルオプションドライバを指定するという習慣に従っていれば、PGF(および TikZ)のドライバ指定も自動的に所望のものになる。以下では、dvipdfmx のドライバを指定してパターンと関数シェーディングを実際に利用する例を挙げる。

\documentclass[a4paper,dvipdfmx]{article} % ドライバをグローバルオプションで指定
\usepackage{tikz}
\usetikzlibrary{patterns,shadings}
\begin{document}
% パターンの例
\tikz \draw[pattern=crosshatch,pattern color=blue] (0,0) rectangle (2,2);
% 関数シェーディングの例
\tikz \shade[shading=Mandelbrot set] (0,0) rectangle (2,2);
\end{document}
f:id:zrbabbler:20130222102650p:image
補足:XeTeX について

XeTeX の現状のドライバ(pdfsys-xetex.def)は(古い)dvipdfm のドライバと同程度の機能しか提供していない。しかし、XeTeX は「dvipdfmx の拡張版」を用いて PDF 生成を行っているので、dvipdfmx 用のドライバとほぼ同じ実装で同等の機能が実現できるはずである。PGF 開発のサイトSourceForge)を見ると、ごく最近(2012 年 11 月)に XeTeX 用ドライバの更新が行われていることが解る。(参照:レポジトリML)これがリリースされれば、XeTeX でも PGF/TikZ のほぼ完全な機能が使えることになるだろう。

(続く)

*1ビットマップ画像の一部のピクセルを透過させる機能。PGF の \pdfdeclaremask 命令。

*2:領域を「模様で」塗りつぶす機能。TikZ の patterns ライブラリ

*3:例えば領域をマンデルブロ集合で塗りつぶすための機能(謎)。TikZ の shadings ライブラリ

*4:かつて「dvipdfmx のバグを回避するために代わりに dvipdfm を使う」というバッドノウハウが用いられていたが、それは現在ではもう通用しないだろう。

*5:dvipdfmx のドライバが作られたのは 2008 年頃だと思う。

*6:これは graphicx 用の xbb 自動生成などがそのまま適用されていることから判断できる。

*7:pdfTeX は「DVI ドライバ」相当部分と「TeX エンジン」相当部分の両方を兼ねているので話が解りにくくなっている。

*8:「LaTeX 以外」ではドライバのファイル名(pgfsys-〈ドライバ名〉.def という名前)を著す \pgfsysdriver というマクロを直接再定義することが指示されている。

*9:graphicx は内部で graphics を「同じドライバ指定で」読み込んでいる。

*10文書クラスに指定されたオプション\documentclass 命令のオプション)は、全てのパッケージについて「それが適用可能であれば」適用される。これを「グローバルオプション」と呼ぶことがある。

*11:先に述べた事情のため、昔は dvipdfmx を使うときにパッケージによって「dvipdfmx」と「dvipdfm」の指定を使い分ける必要があって、この方法が使えなった。そのため日本ではあまりこの方式は使われていないようである。

2013-02-21

Beamer は dvipdfmx をサポートするか否か? (2)

前回の続き)
アレをなんとかする話

前回、「ナビゲーションシンボルが dvipdfmx で機能しない問題について、XeTeX では対策が施されている」という話をした。その対策用のコードは非常に簡単なもので実質的には次の数行のコードで済んでいる。

[beamerbasenavigation.sty;215行目〜]
%(ZR註: エンジンが XeTeX である場合以下のコードを実行)
    \def\beamer@linkspace#1{%
      \begin{pgfpicture}{0pt}{-1.5pt}{#1}{5.5pt}
        \pgfsetfillopacity{0}
        \pgftext[x=0pt,y=-1.5pt]{.}
        \pgftext[x=#1,y=5.5pt]{.}
      \end{pgfpicture}}

理由は省略するが、要点としては「見えない文字」を出力する必要があり、そのために PGF の機能を利用しているようだ。なので一見すると、同じ対策をドライバが dvipdfmx の場合にも行えば、dvipdfmx でもナビゲーションシンボルが機能するようになる気がした。で、実際にやってみたが、残念なことに、謎の警告が大量に出て結局シンボルは機能しないままであった。

この対策コードでダメな原因は結局解らないが、要するに「見えない文字」を出せばいいことが解ったので、PGF を使わない別の方法で対策を実装してみたら……。

f:id:zrbabbler:20130220120929p:image

おおお、今度は動いた!(もちろんカーソルが変わるだけでなくてリンクも機能する。*1)というわけで、取りあえずこれをパッケージにしておく。

使い方は単純で、単に \usepackage{bxdpx-beamer} でパッケージを読み込めば、対策が効いてナビゲーションシンボルが機能するようになる。これで問題が無ければ本家に取り込んでもらうことを考えよう。

pLaTeX + dvipdfmx で Beamer する方法

dvipdfmx を使う目的として大きな比重を占めるのが pTeX 系で日本語を使いたいことであろう。ここで pLaTeX + dvipdfmx で和文を問題なく使うための最低限の設定を挙げておく。Beamer では既定で欧文フォントサンセリフ\sffamily)にするので、それに合わせて和文をゴシック\gtfamily)にする必要がある。*2

\documentclass[dvipdfmx]{beamer} % dvipdfmx ドライバを指定
\usepackage{bxdpx-beamer}  % ナビゲーションシンボルを機能させる
\usepackage{pxjahyper}     % しおりの文字化け対策
\usepackage{minijs}        % 和文メトリックの調整(代わりに otf を読み込んでも良い)
\renewcommand{\kanjifamilydefault}{\gtdefault} % 和文既定をゴシックに変更
% 後は欧文の Beamer と同様
\usetheme{Frankfurt}
% ……(以下省略)

Beamer は(間接的にではあるが)ドライバに依存するため、クラスのオプションドライバ指定(dvipdfmx)を行っている。*3

補足:遷移アニメーションは大丈夫

dvipdfmx + Beamer に関する話をもう一つ。美文書(第 5 版)によると、その当時*4は dvipdfmx でページ遷移アニメーション(\transdissolve 等)が機能しなかったらしい。現在はこれは問題なく機能する。5 ヶ月前(2012 年 9 月)に gist に公開したコードを挙げておく。

*1:節単位の移動の挙動がよく解らないけど、とりあえず pdfTeX 生成のものと同じ結果になっている。)

*2:セリフ+明朝でもよいが、サンセリフ系よりも視認性が劣るのでウェイトが太いフォントを選ぶ必要がある。少なくとも CM Roman 系や IPA 明朝やMS明朝は細すぎるので不適切である。

*3:ただし間接的であるので、Beamer の実装コード自体は dvipdfmx や dvips 等のドライバ指定オプションについて何も行わない。Beamer は内部で hyperref や PGF(pgfcore パッケージ)を読み込んでいて、実はドライバオプションはこれらに対する「グローバルオプション」として効いている。

*4:第 5 版は 2010 年 8 月の発行。

2013-02-20

Beamer は dvipdfmx をサポートするか否か? (1)

と書くと、「実際に dvipdfmx で Beamer している」人はあれっと思うかも知れない。しかし、Beamer のマニュアルのほぼ冒頭の箇所(1.1 節)に次のように書かれている。

You can use beamer with pdflatex, latex+dvips, lualatex and xelatex. latex+dvipdfm isn’t supported (but we accept patches!).

果たしてこの「dvipdfmx はサポートされてない」という記述は単に古い情報*1の更新漏れだろうか、それとも今でも dvipdfmx は「公式にサポートされている」と言えない事情があるのだろうか。

dvipdfmx では確かに動かないアレ

現実には、dvipdfmx で Beamer のほとんどの機能は使えているのだが、一つ、dvipdfmx では使えない大事な機能が確かに存在する。それは(標準で)画面の右下に表示される「ナビゲーションシンボル」である。

f:id:zrbabbler:20130220121518p:image

pdfTeX で PDF に変換した場合は、この要素に含まれるアイコンをクリックすることで、スライド・フレーム・節などの単位で移動することができるのであるが、dvipdfmx で変換した場合にはこれが全く働かない。だから、dvipdfmx の使用を前提とする Beamer の解説では、次の記述を行って、無益な「ナビゲーションシンボル」を非表示にすることが指示していることが多い。

\setbeamertemplate{navigation symbols}{}

ところでこの「ナビゲーションシンボル」について、ChoF 氏*2TUGboat の記事 “DVI specials for PDF generation” で面白いことを書いている。

  • Beamer においては、描画機能は PGF、リンク等の PDF 関連の機能を hyperref が担当している。これらの 2 つのパッケージは(標準の DVI を超える機能を扱うので)ドライバに依存する。
  • 実は、Beamer が持つドライバ依存な部分は全て上述の 2 つのパッケージの担当分であり、Beamer 自身は直接にはドライバ拡張機能を使っていない。*3
  • 現在(2009 年)では PGF と hyperref のどちらも「dvipdfmx をサポートしている」。だから、本来は Beamer もそうであるべきである。
  • 「ナビゲーションシンボル」が機能しないのは、dvipdfmx のやや意外な仕様による Beamer のバグが原因である。*4

要するに、「Beamer がどの程度 dvipdfmx に対応しているか」は「PGF/hyperref がどの程度 dvipdfmx に対応しているか」に完全に依拠しているということである。後者については、PGF と hyperref における dvipdfmx の対応の程度は確かに pdfTeX には及ばないが、それでも「対応している」と呼べるほど十分に機能しているように思える。現状で dvipdfmx で使えないのはナビゲーションシンボルに限られるのではないだろうか。

アレが動かないから未対応!?

ところで、最初に挙げた Beamer のマニュアルの文をもう一度見てみると、XeTeX(xelatex)については公式に対応していると見做されていることが判る。ところが、PGF と hyperref のレベルでみると、dvipdfmx と XeTeX のドライバの対応状況はほとんど同じである。(XeTeX は PDF 生成のために「dvipdfmx の拡張版」を利用している。*5)しかし一つ例外があって、XeTeX ではナビゲーションシンボルがちゃんと機能する!*6ここから導かれる帰結は……?

Beamer が dvipdfmx をサポートしないことになっている理由は
「ナビゲーションシンボルが動かない」からである。

ええっ!?

次回に続く)

*1:Tantau が開発を行っていた頃(2007 年、3.07 版まで)の Beamer は確かに dvipdfmx では全く使えなかった。(PGF が非対応だったため。)

*2:dvipdfmx の主要な開発者の一人である Jin-Hwan Cho(趙珍煥)の愛称。

*3:実際、Beamer の実装コードの中には DVI special 命令や \pdf で始まる pdfTeX の拡張プリミティブを呼んでいる箇所は全く存在しない。(pdfTeX 専用の補助パッケージを除く。)

*4:さらに、ChoF 氏はこの不幸なバグが生まれたのは、dvipdfmx の仕様を著した文書が決定的に不足しているからと述べている。そして、当該の記事の本題はまさにその「dvipdfmx の仕様」なのである。

*5:XeTeX は TeX 文書をコンパイルするとき、まず独自拡張の DVI 形式のデータを生成して、それを「dvipdfmx の拡張版」である xdvipdfmx というコマンドで PDF データに変換するという処理を内部で行っている。従って、ドライバ拡張機能については dvipdfmx と XeTeX(xdvipdfmx)は共通する部分が多い。(でも相違点も結構多いような気がする。)

*6:XeTeX が「dvipdfmx の拡張版」を用いているので、本来なら「dvipdfmx と全く同じ理由で」XeTeX でもナビゲーションシンボルは機能しないはずである。これが機能するのは、XeTeX に関しては対策用のコードが加えられているから。

2013-02-15

アレだった XeTeX の縦書きがアレになった

最近に W32TeX を最新版に更新したのだが、そうすると、XeTeX の縦書き(の横転出力)の動作が変わっていることに気が付いた。例えば、次の単純な plain TeX 文書で試してみる。

% plain XeTeX 文書; 文字コードは UTF-8
\font\fLatn="LMRoman10-Regular"
\font\fHorz="IPAMincho:script=hani"
\font\fVert="IPAMincho:script=hani,vertical,+vert,+vrt2"
\fLatn Latin\quad \fHorz 横書ーき\quad \fVert 縦書ーき
\bye

結果は以下のようになる。ここで「(新)」が 2013/02/09 版の W32TeX のもの、「(旧)」が TeX Live 2012 のものである。*1(なお、以下の説明では上下左右について「横転したまま」の状態を基準にする。)

f:id:zrbabbler:20130216034027p:image

これを見ると、縦書き和文の基準線の配置が変わっていることが判る:(旧)は基準線*2を欧文ベースラインと合わせているのに対し、(新)では仮想ボディ下端の線を欧文ベースラインと合わせている。*3ただ、縦書きで和欧文混植を行うときの状況を考えると、何れにしても欧文と和文の垂直位置は合っていない。(それから、(新)では〈縦〉と〈書〉の間が妙に詰まってしまっている。)

ところで、現実に日本語(の文)を出力する場合には、和文文字の間で行分割できるように、間にグルーを入れる必要がある。XeTeX ではエンジンにそれを自動で行う機能があるので、それを有効にしてみる。

% plain XeTeX 文書; 文字コードは UTF-8
\XeTeXlinebreaklocale "ja" % 行分割を可能にする
\font\fLatn="LMRoman10-Regular"
\font\fHorz="IPAMincho:script=hani"
\font\fVert="IPAMincho:script=hani,vertical,+vert,+vrt2"
\fLatn Latin\quad \fHorz 横書ーき\quad \fVert 縦書ーき
\bye
f:id:zrbabbler:20130216034026p:image

(新)の方は組版が完全に崩壊してしまった。どうやら、一文字ずつ出力する場合には、*4グリフの横幅の情報が異常になっているようにみえる。

そこで、グリフのメトリック(内部処理上の寸法)を調べるために、タイトな枠で字を囲って出力させてみる。(\fbox を使うので今度は LaTeX 文書にする。)

% XeLaTeX 文書; 文字コードは UTF-8
\documentclass[a4paper]{article}
\XeTeXlinebreaklocale "ja"
\font\fLatn="LMRoman10-Regular"
\font\fHorz="IPAMincho:script=hani"
\font\fVert="IPAMincho:script=hani,vertical,+vert,+vrt2" at 50pt
\setlength{\fboxsep}{0pt}
\begin{document}
\fbox{\fLatn A}\quad\fbox{\fVert}\quad
% 漢数字「一」・音引き・全角数字「1」
\fbox{\fVert}\quad\fbox{\fVert}\quad\fbox{\fVert}
\end{document}
f:id:zrbabbler:20130216061503p:image

はっきりいって、どちらもデタラメであるが、次のような感じである。(フォントサイズは 10pt である。)

  • (旧)では、「回転する前の縦書き用グリフのメトリック」をそのまま保持している。つまり幅は 10pt で、高さは〈あ〉が 8.11035pt、漢数字の〈一〉が 4.78027pt。音引きの高さをみると、「縦書きのグリフ」を規準であることが判る。
  • (新)はもっと訳がわからない。高さは「(旧)の値 + 5pt」、深さは常にゼロ、そして幅は全てのグリフについて 1.0pt か 2.0pt の何れかの値になっている。どちらの値になるかの規則はよく解らない。

これを見ると、果たして「本当に XeTeX で縦書きができるのか」と疑問に思えてくる。実際には、xeCJK の使用を前提にして少し補正のコードを書くことで縦書きが実現できるのだが、その話はまた後日。

[追記]念のため、「本来あるべき姿」を表した図を載せておく。(ベースラインシフトは 3.8pt。)

f:id:zrbabbler:20130216200544p:image

*1TeX Live では実行ファイルについては初期リリースから原則的に更新されないので、「少し古い版」ということになる。なお、バージョン番号は(旧)が 0.9998 であるのに対し、(新)は 0.9999 である。ただし、この縦書きの挙動の変更とバージョン番号の変更が連動しているかは不明である。

*2:日本語フォントでは縦書きの場合の基準線の垂直位置は仮想ボディの中心を通る。

*3pTeX 系の \tbaselineshift の値でいうと、(新)は 0.5zw、(旧)は 0zw に相当する。

*4:XeTeX では単語単位で「文字列を内部にもつグリフレンダリングエンジンで処理してグリフの配置情報に変換する」という処理を行っている。最初の例だと「縦書ーき」が全体が「単語」であったのに対し、今の例で、文字間に(行分割のための)グルーを入れると、各文字が「単語」として扱われるので、組版結果が変わる可能性がある。

2013-02-10

文書内でフォントマップを指定する命令

DVI ウェアにおいて、TFM と実物の(アウトライン*1フォントとの対応が記述されるのが「マップファイル」*2である。どのマップファイルを読み込むかは、基本的には各ソフトウェアの設定ファイル*3において指示するが、それとは別に「起動時に」マップファイルを追加で指定できるものもある。例えば、dvipdfmx であれば「-f foo.map」、dvips であれば「-u foo.map」で foo.map が追加で読み込まれる。

pdfTeX でも「起動時」にマップファイルを指定する仕組みがある。といっても、pdfTeX はそもそも TeX 文書から直接 PDF 文書に変換するものなので、「起動時の設定の記述」は TeX 文書中に書くことになる。*4すなわち、TeX 文書において「\pdfmapfile[foo,map}」という命令を実行することでマップファイルの読込を指示できる。また、\pdfmapline というマップ行を直接書くための命令も用意されている。

文書中でフォントマップの設定ができるのは非常に便利なので、後になって、dvipdfmx でも同様の機能が実装された。これは DVI special 命令によって実現していて、マップファイルを指定するのが pdf:mapfile 命令、マップ行そ指定するのが pdf:mapline 命令である。

この記事ではこれらの「文書中でフォントマップを指定する命令」について解説する。特に、その書式の中の「修飾子」がの働きが把握しにくく間違いやすいのでこれを中心に扱う。*5「マップ行」の書式については扱わない。

pdfTeX の \pdfmapfile/\pdfmapline プリミティブ
  • \pdfmapfile{<修飾子><マップファイル名>} : [命令] その場でマップファイルを読み、そこに書かれた各マップ行に基づいてフォントマップを置換/追加/削除する。
  • \pdfmapline{<修飾子><マップ行>} : [命令] 当該のマップ行の記述に基づいてフォントマップを置換/追加/削除する。
  • <修飾子>=+-、無しの何れか。

修飾子による動作の違いを、\pdfmapline について述べると以下のようになる。*6

  • 修飾子が「有る」場合: まず pdftex.map(既定のマップファイル*7)がまだ読まれていない*8ならばそれを読み込む。その後、当該のマップ行を処理する。
    • 修飾子が「=」(置換): 当該の TFM に対するマップが既に存在するか否かに関わらず、マップを指定されたものに設定する。
    • 修飾子が「+」(追加): 当該の TFM に対するマップが存在しないならばマップを指定されたものに設定する。存在するならば無視する(警告が出る)。
    • 修飾子が「-」(削除): 当該の TFM に対するマップが存在するならばそれを削除する。(つまり命令のマップ行の記述は無関係。)
  • 修飾子が「無い」場合: pdftex.map(既定のマップファイル)の読込を放棄する。*9その後、当該のマップ行を「追加」(つまり修飾子 + と同じ動作)する。
dvipdfmx の pdf:mapfile/pdf:mapline special 命令

DVI special なので、\AtBeginDvi{\special{pdf:mapfile foo.map}} のように用いられるのが通例である。

  • pdf:mapfile <修飾子><マップファイル名> : その場でマップファイルを読み、そこに書かれた各マップ行に基づいてフォントマップを置換/追加/削除する。
  • pdf:mapline <修飾子><マップ行> : 当該のマップ行の記述に基づいてフォントマップを置換/追加/削除する。
  • <修飾子>+-、無しの何れか。(= は存在せず、「無し」が「置換」を意味する。)

dvipdfmx では、「既定のマップファイルの読込の放棄」という概念は存在しない。設定ファイルや起動時オプションで指定されたマップファイル*10は常に読み込まれる。修飾子の意味は次の通り。

  • 修飾子が無い(置換): pdfTeX の「置換」と同じ。
  • 修飾子が「+」(追加): pdfTeX の「追加」と同じだが、警告は出ない。
  • 修飾子が「-」(削除): pdfTeX の「削除」と同じ。
要するに

ほとんどの場合、意図している動作は「置換」であろう。マップファイルを「置換」で読み込む場合、以下のようにすればよい。

  • pdfTeX の場合:
    \pdfmapfile{=foo.map} % 修飾子は「=」
    
  • dvipdfmx の場合:
    \AtBeginDvi{\special{pdf:mapfile foo.map}} % 修飾子は無し
    

*1:大抵の DVI ウェアは、自身のマップ中に対応するフォントが見つからない場合は、mktexpk を実行して PK ファイルを使用する。

*2DVI ウェアによって書式に違いがあるが、(標準の)ファイル拡張子はどれも .map である。

*3:dvips では config.ps、dvipdfmx では dvipdfmx.cfg というファイル。

*4:dvips や dvipdfmx の「起動時オプション」による設定に相当するものは、pdfTeX では「TeX の(拡張)プリミティブ」になっていることが多い。例えば、出力する PDF のバージョンを 1.5 にしたい場合、dvipdfmx では起動時に「-V 5」と指定するが、pdfTeX では TeX 文書で「\pdfminorversion=5」を実行することになる。

*5:この命令を使うときに、私はいつでも修飾子で迷っている。ところで、dvipdfmx のフォントマップ命令の「修飾子」の説明ってどこかにあるのですかね? 私は見つけられなかったので、プログラムソースと実験で把握した。

*6\pdfmapfile については、ファイル中の全ての行に同じ修飾子が付されていると考えればよい。

*7:この「既定値」は固定されていて設定ファイル等で変更することはできない。

*8:かつ「放棄」もされていない。

*9:pdftex.map の読込のタイミングは以下のようになる。「\pdfmapfile\pdfmapline の実行」「マップ情報への参照」の何れかが最初に起こったときに行われる。ただしそれが「修飾子なしの \pdfmap〜」だった場合は結局読み込まれない。

*10:ちなみに、これらは「置換」として読み込まれる。すなわち、同じ TFM がある場合は後のものが有効になる。

2013-02-09

CJK パッケージ入門的な何か(1)

この記事の内容は古くなっています。CJK パッケージの解説(特に日本語を扱う話)については以下の記事を参照してください。

なお、本記事で紹介されている“bxcjkipafont パッケージ”を発展させたものが bxcjkjatype パッケージです。

CJK パッケージとは、8 ビット欧文 TeX エンジン上の LaTeX において CJK 言語(中国語・日本語・韓国語)を扱うためのパッケージとして最もよく使われているものである。ここで、「8 ビット欧文 TeX」というのは、pTeX 系でも UnicodeTeX(XeTeX/LuaTeX)でもないということで、LaTeX でいえばオリジナルの LaTeXlatex)と pdfLaTeX(pdflatex)が該当する。主に欧米や中国において CJK 言語を含む LaTeX 文書を作成するのに用いられている。*1

他の(pTeX 系/Unicode TeX)エンジンの何れかが使用可能な状況では CJK パッケージを特に選択する理由はない。日本語に関しては pTeX 系の方が機能も品質も優れているし、また、少なくとも現在は、CJK 言語全般に関して pTeX 系も Unicode TeX も CJK パッケージよりも優れているからである。実際、欧米や中国でも最近は Unicode TeX、特に XeLaTeX + xeCJK パッケージの利用に徐々に移行しつつあるようである。しかし、どうしても 8 ビット欧文 TeX を使わざるを得ない状況も確かに存在する。

  • TeX を内部で使用する文書作成(等の)ツール」において 8 ビット欧文 TeX しかサポートされていない。
  • TeXコンパイルを提供するオンラインのサービス」において 8 ビット欧文 TeX しかサポートされていない。
  • pdfTeX 特有の機能を使いたい。*2

従って、これに該当する状況で日本語(等の CJK 言語)を含む LaTeX 文書を作りたい場合には、今でも CJK パッケージは有望な選択肢となるだろう。

この記事では、現時点で最適と考えられる方式での CJK パッケージの使用法を解説する。ただし、CJK パッケージは非常に複雑なので、少しでも理解を容易にするため、以下のような前提条件を設ける。

インストール

この記事で扱う使用法の範囲で必要になるものは、CTAN のパッケージ名でいうと以下のようになる。最近(1 年以内)の TeX Live であれば全て含まれているはずである。*6W32TeX にはどれも含まれないので自分でインストールする必要がある。最低限必要なファイルをインストールする手順を併せて記した。

IPA フォントTeX Live には既に含まれている。インストール済みでない場合は配布元から入手して(OS に)インストールしておく必要がある。

また、CJK パッケージでの「IPA フォント」の使用を補助するためのパッケージを用意したので、これもインストールする。
後ほど公開します

  • bxcjkipafont パッケージ (gist/zr-tex8r)
    bxcjkipafont.sty を $TEXMF/tex/latex/bxcjkipafont/ に移動。
最も基本的な使用法

CJK パッケージを使用した文書の作成方法を説明するために、以下に単純な文書の例を示す。

[test-cjk.tex]
% 文字コードは UTF-8 (BOM無)
\documentclass[a4paper]{article}
\usepackage{CJK,CJKspace,CJKpunct,bxcjkipafont}
\begin{document}
Here is a Japanese passage:

\begin{CJK*}{UTF8}{ipam}
「ただ今、CJK パッケージの
テスト中。」
\end{CJK*}
\end{document}

コンパイル組版)の方法は欧文の LaTeX 文書と同じである。

  • dvipdfmx の場合: 「latex test-cjk」で DVI ファイルに変換、その後「dvipdfmx test-cjk」で PDF 文書に変換する。
  • pdfTeX の場合: 「pdflatex test-cjk」で PDF 文書に変換する。
f:id:zrbabbler:20130203233547p:image

特に注意すべき要点は次の通り。

  • ファイルの文字コードBOM 無しの UTF-8 にする。*9
  • 文書クラスは pLaTeX 専用のもの(jsarticle 等)以外は任意のものが使える。
  • CJK パッケージとともに必ず CJKspace、CJKpunct を読み込む。*10CJKpunct パッケージを読み込むと約物の周りの空きが自動調整される。例えば上の例では〈。」〉の箇所で半角詰められている。CJKspace パッケージについては後で少し説明する。
  • bxcjkipafont パッケージを読み込むと、IPA フォントのファミリ(ipam、ipag)が使えるようになる。それ以外のフォントの利用については後で説明する予定である。
  • 原則として、日本語(CJK 言語)の文字を含むテキストは全て CJK* 環境*11の中に含める必要がある。

CJK* 環境の書式は以下の通り。

\begin{CJK*}{UTF8}{<ファミリ>}
〈CJK言語のテキスト〉
\end{CJK*}

ここで <ファミリ> は CJK フォントファミリの名前で、bxcjkipafont 読込時には以下のものが指定できる。

この <ファミリ> は省略する(空にする)ことも可能で、その場合の既定値は ipam となる。*12最初の引数UTF8エンコーディング名であり、CJK パッケージでは UTF-8 以外のエンコーディングもサポートされているが、この記事では UTF-8 しか使わない前提なので必ず UTF8 とする。

なお、「CJK の文字は全て CJK* の中に限られる」という制限は、その場で出力されるテキストに限らず、例えば、

\date{1年前の今日}
\newcommand{\sampleText}{本日は豚天なり。}
\renewcommand{\figurename}{ふぃぎゃー}

のような定義文も含む。欧文の UTF-8 入力(inputenc 使用時)との競合が問題にならないならば、本文全体を CJK* 環境に入れてしまっても構わない。プリアンブルに CJK* 環境を置くことはできないので、必ず本文に含めることになる。

フォントの選択

CJK* 環境内で、\CJKfamily{<ファミリ>} で CJK フォントファミリを*13変更できる。bxcjkipafont パッケージでは IPA フォント用のファミリ ipam と ipag を用意しているが、この他に CJK パッケージで予め定義されたものもある。詳細は後で扱う。

なお、bxcjkipafont パッケージでは、ファミリを ipam/ipag に切り替えるための命令 \ipamfamily\ipagfamily が用意されている。

シリーズとシェープは欧文と連動している。つまり欧文用のフォント切替命令(\bfseries\textsl 等)を用いて切り替えることができる。*14

[test-cjk2.tex]
% 文字コードは UTF-8 (BOM無)
\documentclass[a4paper]{article}
\usepackage{CJK,CJKspace,CJKpunct}
\usepackage[fancy]{bxcjkipafont}
\begin{document}
\begin{CJK*}{UTF8}{} % 既定は ipam
(明朝){\ipagfamily (ゴシック)}%
直立体 upright \textsl{斜体 oblique}
\end{CJK*}
\end{document}
f:id:zrbabbler:20130203233546p:image

*1韓国では HLaTeX というパッケージ(これも 8 ビット欧文 TeX エンジン上で動作する)が広く使われている。もちろん日本で使われているのは pTeX 系である。

*2:原理的には pdfTeX の機能はすべて LuaTeX でも実現できるのであるが、LaTeX のパッケージに関しては、pdfTeX にのみ対応(LuaTeX は不可)のものが存在する。

*3Windows ユーザはシフト JIS の方が便利かも知れないが、いわゆる「ダメ文字」の問題があって CJK パッケージでシフト JIS を使おうとすると変換フィルタを途中に挟む形になる。どうせ変換が必要になるのなら、UTF-8文字コード変換をする方が賢明であろう。

*4:CJK パッケージの日本語フォントファミリ(min/goth)の実体として本来想定されているものは大日本印刷フォント(多分 NTT jTeX 用の PK フォントじゃないかな?)であり、「和田研フォント」は飽くまで「手元での組版の確認のための」代用である、というのが実状のようである。もちろん今では「本来の」結果を得るのは不可能なので、既定の日本語フォントファミリは諦めて、「IPA フォント」用のファミリを用意することになる。

*5:dvips では「Unicode サブフォント TFM + TrueType/OpenType フォント」という組み合わせは扱えない。

*6DebianOS の texlive パッケージでは何故か cjkpunct が欠落しているらしい。

*7:残りのファイル・ディレクトリは(この記事の使用法では)不要。TeX Live では cjk 直下に contrib, texinput, utils がある。

*8アーカイブのファイルの配置が TDS に従ったものになっている。

*9BOM 有りでも通るシステムもあるが確実ではない。

*10:CJKspace パッケージは CJK のアーカイブに一緒に含まれている。

*11:または(後で登場する)CJK 環境。

*12:ただし bxcjkipafont パッケージの mainfamily オプションで変更可能。なお、bxcjkipafont 非使用時は CJK パッケージで元から定義されている “song” がファミリの既定値となる。

*13:CJK 用のフォントファミリは欧文のフォントファミリとは別箇に管理されていて、この 2 つのフォントファミリは独立して変更することができる。この仕組は pTeX 系と同じである。

*14:ただし、日本語では斜体は普通は用いられず、また IPA フォントには「太字」のフォントがないので、ipam/ipag ファミリでは既定ではシリーズ・シェープの切り替えは無効にしている。bxcjkipafont パッケージのオプションfancy を指定すると、斜体(\slshape)と太字(bfseries)の指定が有効になる。ただし、斜体は dvipdfmx でしか実現できない。また、太字は(CJK パッケージの機能を使って)「重ねうち」で実現しているので十分に注意すること。

2013-02-08

Beamer の frame の fragile

fragile オプションのややこしい話

マニュアルで説明されているように、Beamer のフレーム記述(frame 環境)の中に verbatim 環境を含める場合は、環境のオプションとして fragile を指定する必要がある。

\documentclass{beamer}
\begin{document}
% ↓fragile オプションが必要!
\begin{frame}[fragile]{You know\ldots}
  Of course, all {\TeX} learners \alert{must}
  see what this means:
  \par\small
\begin{verbatim}
\begingroup\expandfater\expandfater\expandfater\endgroup
\expandafter\ifx\csname xx@opt@\xx@opt\endcsname\relax
...
\fi
\end{verbatim}
\end{frame}
\end{document}

マニュアルをよく読むと解るが、fragile オプションには singleslide という値を設定することができる。つまり、fragile の「値」は次の 3 種類があることになる。

この値により、フレームの中身の内部処理に違いがあるらしい。仕様としては以下の点が異なる。*2

  • 「指定なし」の場合:

    verbatim 環境は使えない。

  • fragile」の場合:

    verbatim 環境が使えるようになる。フレームの中身を一旦ファイルに書き出してそれを改めて読み込むという処理が行われるらしい。このため、処理が若干重くなる。また、処理の都合上、次のような制約が生じる。*3

    フレームの内容に \end{frame} だけからなる行を含められない。

    もちろん、frame 環境をネストさせることはないはずである。しかし、Beamer の解説のプレゼンを作っていて verbatim 環境の中に \end{frame} と書きたいという場合には困ることになる。

  • fragile=singleslide」の場合:

    verbatim 環境が使えるようになる。また fragile が持つ欠点や制約はこちらには当てはまらない。代わりに次のような制約が生じる。

    複数スライドからなるフレームが作れない。つまり \pause 等が使えない。

    だから、この指定と fragile 指定を使い分ける必要があるかも知れない。

frame 環境で「\end{frame}」と書くための素敵な方法

ところで、先に説明した「制約」を考え合わせると、「複数スライドを生じるフレームの中に verbatim があってその中に \end{frame} だけの行が存在する」という非常に不幸な状況では fragile の値をどれにしても解決できないことになる。

\documentclass{beamer}
\begin{document}
\begin{frame}[fragile]{Beamer is easy}
  Making a slide is as easy as follows:\par
  %↓オーバレイを使った
  \begin{quote}<2>\small
%↓verbatim があって中に \end{frame} がある
\begin{verbatim}
\begin{frame}{Slide title}
  ...Content...
\end{frame}
\end{verbatim}
  \end{quote}
\end{frame}
\end{document}

\end{Frame} の前後に何か文字があれば問題ないのであるが、verbatim の中だから当然追加した文字はそのまま出力されてしまう。すると、「後ろに空白文字を入れる(これなら見た目が変わらない)」という方策を思いつくが、残念ながら TeX の仕様による制約のため上手くいかない。*4何か素敵な解決法はないものか。

そう、素敵な解決法といえば、半角カナを使って……

……じゃなくて、frame 環境の environment オプションを利用するのが正解。

\documentclass{beamer}
\newenvironment{specialframe}
  {\begin{frame}[fragile,environment=specialframe]}
  {\end{frame}}
\begin{document}
\begin{specialframe}{Beamer is easy}
  Making a slide is as easy as follows:\par
  \begin{quote}<2>\small
\begin{verbatim}
\begin{frame}{Slide title}
  ...Content...
\end{frame}
\end{verbatim}
  \end{quote}
\end{specialframe}
\end{document}

frame 環境に展開される「specialframe 環境」を定義する。その際に、frame 環境に environment=specialframe というオプションを指定する。これで、Beamer はフレームの終端を「\end{specialframe}」という行(実際に終端として書かれるのはそれのはずである)で判断するようになる。すなわち、「\end{frame}」の行は安全に含めることができる。

*1:containsverbatim は fragile=singleslide の設定に対する旧版での名前であるが、現在でもサポートされている。

*2:もちろんマニュアルに記載されている。

*3:これは、fragile 指定時には、フレームの終端を「\end{frame}」の行で判断するという処理が行われるからである。

*4:行末にある空白文字は、例え(verbatim の中のように)空白文字のカテゴリコードが 10 以外になっていても、無条件に無視されてしまうのである。ただし、全角空白(U+3000)や no-break space(U+00A0)はこの制約を受けないので、その文字が使える環境であれば回避策になりうる。

2013-02-07

verbatim 環境で「\end{verbatim}」と書くための素敵な方法

あまりにも素敵なので、よい子のみなさんはマネしないように。入力漢字コードがシフト JIS である pLaTeX 限定。

% 文字コードはシフトJIS
\documentclass[a4paper]{article}
\usepackage[ascii]{inputenc} % おまじないっ!
%↓↓↓素敵な定義: オワタ を end に変換する.
\def オワタ{end}
%↑↑↑素敵な定義おわり
\begin{document}
You can use \verb|verbatim| environment as follows:
\begin{quote}\small
% end の代わりに オワタ と書けばよい!
\begin{verbatim}
Some text\ldots.
\begin{verbatim}
\expandafter\expandafter\expandafter!!
\オワタ{verbatim}
Some other text\ldots.
\end{verbatim}
\end{quote}
\end{document}
f:id:zrbabbler:20130207023734p:image

大事なのは「オワタ」を半角カナで書くことである。*1ちなみに、欧文 LaTeX であれば、「オワタ」をシフトJISEUC-JPUTF-8 の何れで書いても上手くいく。*2

真面目な解決法

verbatim パッケージを使うとか。

\documentclass[a4paper]{article}
\usepackage{verbatim}
\newenvironment{myverbatim}
  {\verbatim}{\endverbatim}
\begin{document}
You can use \verb|verbatim| environment as follows:
\begin{quote}\small
\begin{myverbatim}
Some text\ldots.
\begin{verbatim}
\expandafter\expandafter\expandafter!!
\end{verbatim}
Some other text\ldots.
\end{myverbatim}
\end{quote}
\end{document}

verbatim を中に含むユーザ定義環境を作る」のは LaTeX で「何故か上手くいかない」ことの有名な例の一つである。しかし、verbatim パッケージを利用すると、上記のコードのようにして「上手くいく」ようになる。*3新しく定義した myverbatim 環境の終端は当然 \end{myverbatim} なので、そこには「\end{verbatim}」が入れられるというわけである。

他の手段として、同じく verbatim パッケージを利用して、\verbatiminput を使うという手もある。

*1:別に「オワタ」でなくても半角カナの文字列であれば何でもよい。

*2:種明かし。「オワタ」はシフトJIS(或いは EUC-JPUTF-8)で高位バイトのみからなる列である。inputenc パッケージを読むと、高位バイトが全てアクティブ(カテゴリコード 13)になるので、「\def オワタ」が「オワタ」の先頭のアクティブ文字(バイト)に対するマクロ定義として通用するのである。verbatim 環境に入っても、高位バイトのカテゴリコードは全く変わらないので、「オワタ」のマクロは有効であり続ける、というのが欧文 TeX で動作する理由。また、シフト JIS 入力の pTeX半角カナの領域(A0h〜DFh)のバイトを和文文字と見做さないので欧文 TeX と同様に正常動作する。EUC-JPUTF-8 の場合は pTeX の和文入力処理と干渉して失敗するようである。

*3\begin{verbatim}\end{verbatim} でなくて \verbatim\endverbatim とする必要がある。詳しくはマニュアルを参照。

2013-02-04

考察:文書情報が文字化けしない hyperref の設定

前回までの「実験」の結果を受けて、hyperref の適切なオプション(無し、unicodepdfencoding=auto *1)の設定が何であるかを考える。できるだけ不正な表示や警告が起こらないものを選ぶと以下に示す設定が最適となる。一般の LaTeX ユーザはこの結果だけを知っていれば十分であろう。

欧文(CJK無し)の文書の場合
  • 欧文 8 ビット TeX*2unicode、または pdfencoding=auto
    pTeX 系でも文書情報に和文文字がないならこちらで構わない。
    ※「OT1 + Latin-1」の範囲の文字のみ使用ならば「無し」でも構わない。
  • XeTeX: 無し、または pdfencoding=auto
  • LuaTeX: unicode、または pdfencoding=auto
和文(または CJK)の文書の場合
  • pTeX: 無し;pxjahyper パッケージを読み込む。
    ※ ただし「JIS X 0208 + ASCII」の範囲外の文字は欠落またはフォールバックの表示となる。
  • upTeX: 無し;pxjahyper パッケージを読み込む。
  • XeTeX*3: 無し、または pdfencoding=auto
  • LuaTeX: unicode、または pdfencoding=auto
  • 欧文 8 ビット TeX + CJK パッケージ:
    • UTF-8 入力の場合: unicode を指定;CJKutf8 パッケージを読み込む。
    • それ以外: CJKbookmarks を指定;その上で hyperref の出力を外部フィルタもしくは(dvipdfmx の場合)pdf:tounicode special を用いて変換されるようにする。

*1:前述の通り、「無し」は pdfencoding=pdfdoc と同じ、unicodepdfencoding=unicode と同じである。

*2LaTeX のコマンド名でいうと latex と pdflatex が該当する。

*3:XeTeX/LuaTeX については、CJK 文字の有無、また CJK の出力を補助するパッケージ(xeCJK や LuaTeX-ja 等)のパッケージの有無に関わらず設定は同じである。

2013-02-03

実験レポート:文書情報が文字化けしない 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」と同じになる。*1これで確かに LaTeX のエラーは出なくなったが、しかし CJK パッケージのない場合と同じということは、Reader で閲覧した時に表示される文字列は全く同じように化けてしまう(「¡Aㆇ B—Ç」となる)ことになる。つまり、この CJKbookmarks だけでは問題は解決していない。

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

  • CJKbookmarks を指定した時の出力は、文字命令(LICR;\textexclamdown)の部分を除けば、「中国語文字コード(GBK や UTF-8)で文字列がそのまま出力されている」と見做せる。これはちょうど日本で pTeX 系を用いた時と同じ状況になっている。
  • 中国では(日本と同じく)文字命令がほとんど使われない。*2
  • 従って、pTeX 系について日本人が用いてきたのと同じ解決法が使えることになる。そして実際に同じ解決法が使われている。
  • GBK について、日本の out2uni と同様の処理をする gbk2uni というフィルタがあり、pdfTeX を使う場合はこれが使われているらしい。
  • そして PDF への変換に dvipdfmx を使う場合は*3 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 に書き出された〈あ〉は出力用文字コード*4UTF-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 のしおりとして表示される文字列は正常に「あ」になるわけである。*5しかし、test.tex の (4) のように入力文字列が文字命令(LICR)を含んでいる場合にはこれでは上手くいかない。何故なら、この場合〈A1 82 A0 20 42 84 C7〉というバイト列をシフト JIS で解釈することになるが、そうすると、本来 PdfDocEncoding で解釈すべき〈A1〉(→〈¡〉)までシフト JIS で解釈されてしまい、結果は半角カナの〈。〉に化けてしまう。*6pTeX 系では文字命令が使われる機会が少ないからあまり問題にならないが、本当は pdf:tounicode special だけでは完全な解決にはならないのである。

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

(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)文字の文字コードは内部漢字コードである。*8

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

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:つまり、CJK パッケージ読込時について、通常は非 ASCII 文字が入力されると CJK パッケージの実装コードが働き、それが hyperref の処理と衝突してエラーになるが、CJKbookmarks 指定時は、CJK 非読込時と同様に当該の文字を表すバイト列が「そのまま」出力されるようになる。今の例だと UTF-8 となるが、例えば入力文字コードEUC-JP にしていた場合は「そのまま」EUC-JP のバイト列が出力される。これは pTeX 系の出力結果とも類似している。

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

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

*4:これは通常は入力用の文字コードと同じであるが、入力文字コード自動判定が有効の場合(W32TeXpTeX は既定で有効になっている)は異なる場合がある。例えば、-kanji=sjisWindows ではこれが既定)としつつ UTF-8 のファイルを(自動判定で)読み込んだ場合は、入力は UTF-8 であるが出力はシフト JIS-kanji の値)となる。

*5PDF 文書中には実際には <FEFF3042> という 16 進文字列リテラルが書き出されている。

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

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

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

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

2013-02-02

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

hyperref パッケージを用いて PDF 文書を作成する時に頻出するトラブルとして、文書情報(「文書のプロパティ」中のタイトル等、あるいは「しおり」)の文字列の文字化けが挙げられる。正しい出力を得るためには、hyperref のオプションや周辺ツールの設定を正しく行う必要があり、その設定は用いる TeX エンジンにより異なる。(日本では)pTeX 系については比較的情報が集まっている*1が、それ以外のエンジンに関する情報が乏しいので纏めて調べてみたい。

hyperref では文書情報の PDF 文字列の出力形式を制御するオプションとして次のものが用意されている。

  • unicode: 「PDF 文字列を Unicode にする」と(だけ)説明されている。
  • CJKbookmarks: CJK パッケージのためのものらしい。
  • pdfencoding=<値>: ここで <値>pdfdocunicodeauto の何れか。

ところが何故か知らないが、これらのオプションが具体的に何をするものかの説明がほとんど見当たらない。pdfencoding に関してはマニュアルに掲載すらされていない。(ChangeLog で幾らか言及されてるが、そこにも「何なのか」の説明はない。)さらに、(周知のとおり)hyperref の実装コードは非常に巨大で複雑なので、コードを追って動作を理解するというのがかなり困難である。仕方がないので、不確実さは増すが、「実験」によって適切な設定を探り出すことにする。

  • 8 ビット欧文 TeXlatex、pdflatex)
  • LuaTeX(lualatex)
  • XeTeX(xlatex)
  • 8 ビット欧文 TeX + CJK パッケージ

pTeX 系は実験によって何かを探究する対象にはしないが、比較のために以下の環境について同じ実験を行ってその結果について既知の知識を補足する。

  • pTeX/upTeX (platex、uplatex)
  • pTeX/upTeX + pxjahyper パッケージ
実験内容
% 文字コードはUTF-8
\documentclass[a4paper]{article}
\usepackage[T1]{fontenc}
\errorcontextlines=9
\usepackage[(オプション)]{hyperref}
%\usepackage{CJK} %<CJK>
%\AtBeginDocument{\begin{CJK*}{UTF8}{gbsn}} %<CJK>
%\AtEndDocument{\end{CJK*}} %<CJK>
%\usepackage{pxjahyper} %<pxjahyper>
\begin{document}
\section{!A B-C}                                      %(1)
\section{\textexclamdown A B\textemdash \c{C}}        %(2)
\section{\textexclamdown A B\textemdash \c{C}\NG}     %(3)
\section{\textexclamdown あ B\textemdash \c{C}\NG}    %(4)
\end{document}

上の文書をコンパイルして、「しおり」付きの PDF 文書を作る。その上で以下のものを調べる。

  • test.out 中に記された「節タイトルの PDF 文字列に相当する」テキストデータ
  • Adobe Reader で閲覧した時に実際に表示される節タイトルの文字列

詳細は以下の通り。

  • 試験対象ごとのコンパイル方法は以下の通り。
    • 8 ビット欧文 TeX: pdflatex(2回)で変換。
    • LuaTeX: lualatex(2回)で変換。
    • XeTeX: xelatex(2回)で変換。
    • 8 ビット欧文 TeX + CJK パッケージ: <CJK> の行を有効にして、pdflatex(2回)で変換。
    • pTeXplatex(2回)→ dvipdfmx で変換。
    • pTeX + pxjahyper パッケージ: <pxjahyper> の行を有効にして、platex(2回)→ dvipdfmx で変換。
    • upTeX + pxjahyper パッケージ: <pxjahyper> の行を有効にして、uplatex(2回)→ dvipdfmx で変換。
  • (オプション) については次のものを試す。なお、DVI 出力のエンジンを用いる場合は併せて dvipdfmx オプションを指定する。
    • (無し)
    • unicode
    • pdfencoding=pdfdoc
    • pdfencoding=unicode
    • pdfencoding=auto
  • (1)〜(4) の各節タイトルのテキストは以下のような性質をもつ。
    • (1) は ASCII のみからなる。
    • (2) は ASCII 外の文字を含むが PdfDocEncoding の範囲に収まる。
    • (3) は PdfDocEncoding にない文字を含む。(CJK 文字を含まない)
    • (4) は pTeX 系エンジンおよび CJK パッケージが「CJK 文字」として扱う文字を含む。
    \textexclamdown は〈¡〉(U+00A1)、\textemdash は〈—〉(EM DASH;U+2014)、\c{C} は〈&#c7;〉(U+00C7)、\NG は〈Ŋ〉(U+014A)を表す。〈あ〉の符号値は U+3042 である。)
  • XeTeX 及び LuaTeX 上の CJK を扱うパッケージ(xeCJK、LuaTeX-ja 等)を用いた場合については、PDF 文字列においては、CJK 文字もそれ以外の文字も同じ扱いになる(パッケージ不使用の場合と同じになる)はずである。*2
  • 上の文書では(欧文の)特殊文字を文字命令(LICR)で入力している。inputenc パッケージを用いて非 ASCII 文字を直接入力した場合も結局は文字命令に展開される(例えば〈¡〉は \textexclamdown に一度展開される)ので同じ結果のはずである。
  • 8 ビット欧文 TeX(CJK パッケージなし)の場合は、そもそも UTF-8 の直接入力が無効なので、(4) のような非 ASCII 文字を含むテキスト入力は不適切である。少なくとも〈あ〉の文字を入力したとは見做されない。*3
前提知識:PDF文字列のエンコーディング

PDF の仕様では、PDF 文字列(本来はバイト列)を「文字列」として解釈する場合は以下のようなエンコーディングを仮定している。

  • 先頭 2 バイトが〈FE FF〉(UTF-16BE の BOM に相当する)である場合は、UTF-16BE を仮定する。(BOM を除いたのが文字列となる。)
    • <FEFF00500440046658766F8> → 「PDF文書」
    • (\376\377\003\265\000-\000T\000e\000X) → 「ε-TeX
  • それ以外は、PdfDocEncoding という特定のエンコーディングを仮定する。
    • <41646f6280> → 「Adob€」
    • (\050u\051pTeX) → 「(u)pTeX
  • pdfencoding キーの値 unicode は UTF-16BE、pdfdoc は PdfDocEncoding を指していることが予想される。

hyperref が out ファイルに書き出す「結果のテキスト」は通常の(16 進でない)文字列リテラル( ) 内の文字列である。原則としてこれは PDF ファイルにそのまま書き出されて文字列リテラルとして解釈されるので、上記の何れかの符号化に従うものでなければならない。ただし、環境によっては、PDF に変換する際に「結果のテキスト」の加工が行われることがある(pdf:tounicode special 使用時など)。

結果:全般

実験した全ての環境において以下が成立した。

従って、以降では各環境において pdfencoding の値を pdfdocunicodeauto の各々にした場合の結果を列挙する。結果は次の書式で示すことにする。

先頭の番号 (2) は test.tex 中の行 (2) に対応する結果であることを表す。続く ( ) の中にあるのが test.out に出力されたバイト列である。この列がある文字列をある符号化で表したものに等しい場合は、その文字列を記し、後ろに符号化の名前を補足した。(ASCII の場合は省略した。)*5下の行の 「 」 内は Adobe Reader で閲覧したときに実際にしおりに表示された文字列を示している。これは上のリテラルを PdfDocEncoding(pdfdoc)か UTF-16BE(unicode)の何れかで解釈したものに等しいはずであり、何れであるかを後ろに補足した。

8 ビット欧文 TeX/pdfencoding=pdfdoc
  • (1) (!A B-C)
    → 「!A B-C」[pdfdoc]
  • (2) (\241A B\204\307)
    → 「¡A B—Ç」[pdfdoc]
  • (3) (\241A B\204\307)
    → 「¡A B—Ç」[pdfdoc]
  • (4) (\241あ B\204\307) [UTF-8]
    → 「¡Aㆇ B—Ç」[pdfdoc]

pdfencoding=pdfdoc とすると PdfDocEncoding のリテラルが出力された。(3)(4) では PdfDocEncoding にない〈Ŋ〉が欠落している。(コンパイル時にその旨の警告が出ている。)前述の通り (4) での〈あ〉の入力は不正であるが、その結果は〈A2 81 82〉というバイト列を PdfDocEncoding で解釈したものになった。(何れにしても (4) の入力は無意味である。)

8 ビット欧文 TeX/pdfencoding=unicode
  • (1) (\376\377\000!\000A\000\040\000B\000-\000C)
    → 「!A B-C」[unicode]
  • (2) (\376\377\000\241\000A\000\040\000B\040\024\000\307)
    → 「¡A B—Ç」[unicode]
  • (3) (\376\377\000\241\000A\000\040\000B\040\024\000\307\001\112)
    → 「¡A B—ÇŊ」[unicode]
  • (4) (\376\377\000\241\000?\000?\000?\000\040\000B\040\024\000\307\001\112)
    → 「¡Aã B—ÇŊ」[unicode]

pdfencoding=unicode とすると UTF-16BE のリテラルが出力された。今度は〈Ŋ〉の文字も正しく出力されている。*6

8 ビット欧文 TeX/pdfencoding=auto
  • (1) (!A B-C)
    → 「!A B-C」[pdfdoc]
  • (2) (\241A B\204\307)
    → 「¡A B—Ç」[pdfdoc]
  • (3) (\376\377\000\241\000A\000\040\000B\040\024\000\307\001\112)
    → 「¡A B—ÇŊ」[unicode]
  • (4) (\376\377\000\241\000?\000?\000?\000\040\000B\040\024\000\307\001\112)
    → 「¡Aã B—ÇŊ」[unicode]

(1) と (2) は pdfencoding=pdfdoc、(3) と (4) は pdfencoding=unicode と同じ結果になった。つまり、pdfencoding=auto は「PdfDocEncoding で表せる場合はそれで、それ以外は UTF-16BE で表す」ことを意味していると推定される。

LuaTeX/pdfencoding=pdfdoc

「8 ビット欧文 TeX/pdfencoding=pdfdoc」と全く同じ結果になった。注意すべきなのは、UTF-8 入力が前提の LuaTeX の場合は (4) の行も正当な入力(つまり〈あ〉を含む文字列)であるということである。従って、LuaTeX の場合は「(4) は正しく処理できなかった」という結論になる。

LuaTeX/pdfencoding=unicode

(4) 以外は「8 ビット欧文 TeX/pdfencoding=pdfdoc」と全く同じ結果になった。

  • (4) (\376\377\000\241\060\102\000\040\000B\040\024\000\307\001\112)
    → 「¡あ B—ÇŊ」[unicode]

こちらは〈あ〉の文字も正しく処理されている。*7

LuaTeX/pdfencoding=auto

(4) 以外は「8 ビット欧文 TeX/pdfencoding=auto」と全く同じ結果。(4) は「LuaTeX/pdfencoding=unicode」と同じ結果。すなわち、これも全ての入力が正しく処理されている。

XeTeX/pdfencoding=pdfdoc
  • (1) (!A\040B-C)
    → 「!A B-C」 [??]
  • (2) (¡A B—Ç) [UTF-8]
    → 「¡A B—Ç」 [??]
  • (3) (¡A B—ÇŊ) [UTF-8]
    → 「¡A B—ÇŊ」 [??]
  • (4) (¡あ B—ÇŊ) [UTF-8]
    → 「¡あ B—ÇŊ」 [??]

この場合、全ての入力が正しく処理された。しかし .out の出力を見ると、不合理なことに、UTF-8 の文字列そのまま出力されて、これだと本来は Reader で閲覧したときの文字列は化けるはずである。それが正しく表示できるということは、xdvipdfmx がリテラル文字列について何らかの加工を行ったことが判る。これはちょうど dvipdfmx で UTF8-UCS2 の ToUnicode マップを指定した時の動作と等価であるが、xdvipdfmx は特に UTF8-UCS2 の CMap ファイルは使用していないようである。恐らく、xdvipdfmx では「UTF-8 → UTF-16BE の変換を行う」という仕様が規定されていて、なおかつ、hyperref もそれを前提として UTF-8 の文字列をそのまま出力しているのだと思われる。

XeTeX/pdfencoding=unicode

「LuaTeX/pdfencoding=unicode」と同じ結果。つまり、リテラル文字列が(先頭の BOM から)元々 UTF-16BE であると判断できる場合は、「変換」を行わず、正しい表示が得られている。*8

XeTeX/pdfencoding=auto

「XeTeX/pdfencoding=pdfdoc」と同じ結果。*9

続く

*1:(u)pLaTeX + dvipdfmx の場合は要するに pxjahyper パッケージを読み込めばそれで一件落着。

*2:このことはこれらのパッケージの処理過程から推定できる。

*3:〈A3 81 82〉というバイト列が「直接出力」される状態になっている。

*4:さらに、hyperref のオプションunicodepdfencoding=pdfdoc を両方指定すると、後で指定したものが有効になる。ここから、この 2 つが同系統のオプションであることが推定できる。

*5:つまりこの例の場合、test.out に出力されたバイト列は〈66 6F 6F A2 81 82〉である。

*6:なお、(4) の「結果」の中の 3 つの ? はそれぞれ A2、81、82 のバイトである。\000?... の部分の 6 バイトは〈00A2 0081 0082〉という 3 文字に解釈されるはずだが、U+0081 と U+0082 は制御コードなので画面上では U+00A2〈¢〉だけが見えている。繰り返すが、(4) の入力は無意味である。

*7\060\102 が〈あ〉(U+3042)の UTF-16BE 表現になっている。

*8:ただし、この際に「UTF-16 への変換が失敗した」という旨の警告が出ているので、もしかしたら、これは「想定外」の動作なのかも知れない。

*9pdfencoding=unicode の場合も正しい表示になるが警告が出る。だから hyperref では pdfencoding=pdfdoc の結果が想定される入力と判断したのかも知れない。