2014/12/24

TeXの組版で1行ごとに色を変える

テキストからなるドキュメントのページというのは、行の集まりだと考えられます。 では、ページの行ごとに装飾を施すようなことは可能でしょうか? たとえば、本文を行ごとに色分けするようなことは可能でしょうか?

Webページでは、CSSを使って、表の各行の背景色をストライプにするような効果がよく使われています。 しかし、本文を行ごとに色分けするのは、CSSだけではかなり難しそうです。 ブラウザで本文の1行になるコンテンツをどう決めているのか知りませんが、それを制御しようと思ったら、ブラウザの描画処理に介入する方法が必要に思えます。

この事情は (La)TeX でも変わりません。 1行ごとに色を変えるといった処理を実現するには、行とページを組み立てる TeX の仕組みに介入する手立てが必要です。 TeXでは、その手立てが「出力ルーチン(アウトプットルーチン)」という形で提供されています。

ここでは、TeX & LaTeX Advent Calendar 2014 第24日のネタとして、行ストライプ問題を使って出力ルーチンで遊んでみます (第23日は tkz-fct パッケージがアレだった件、明日最終日はZRさんです)。

出力ルーチンとは

ものすごく端折って言うと、TeXは入力ソースを読み込んだら最適な改行位置を計算して各行を作ります。 その各行が溜まってきたら、そこから最適な改ページ位置を計算して1ページを作ります。 各行が溜まったものは「メイン垂直リスト」、そこからTeXが計算して作った1ページは「\box255」と呼ばれます。

しかし、\box255 がそのまま DVI ファイルや PDF ファイルの 1ページにはなるわけではありません。 \box255 を調理して DVI ファイルや PDF ファイルの 1ページにするのが「出力ルーチン」の仕事です。 ページノンブルを振ったり、柱を作ったりする仕事も、実は出力ルーチンで行われています。

この出力ルーチンにおける処理の内容は、ユーザが好きなように定義できます。 その定義では、\box255 をそのまま律儀に使う必要もありません。 たとえば、出力するページに残したい行だけを取り出して残りはメイン垂直リストに戻すことも可能です(改めて \box255 が組み立てられるので、戻した行は次ページ以降の材料として使われる)。 残したい行だけを取り出すときには、\vsplit というプリミティブが使えます。 「\vsplit ボックス to 高さ」のように実行すれば、指定したボックスから指定した高さぶんの行を取り出してきたボックスが手に入ります。

本文をクリスマスっぽいストライプにする

ここまでくれば、本文の行をストライプにする方法が見えてきましたね。 \box255 から \vsplit で1行ずつ取り出して交互に色を付け、それを集めたボックスを作り、そのボックスを出力ページとして吐き出せばよさそうです。

\documentclass[12pt]{article}

\usepackage{lipsum}
\usepackage[usenames,dvipsnames]{xcolor}

\fboxsep=0pt
\newcount\RGF
\RGF=0
\def\XmasStripe#1{
  \ifnum\RGF=0
    \colorbox{red}{\color{ForestGreen}#1}
    \global\RGF=1
  \else
    \colorbox{ForestGreen}{\color{Red}#1}
    \global\RGF=0
  \fi}

\output={ \loop \setbox1=\vsplit255 to\baselineskip \setbox0=\vbox{\copy0\XmasStripe{\copy1}\vskip-\dp1} \ifdim\ht255>0pt \repeat \shipout\box0}
\begin{document} \color{ForestGreen} % 最初の行のため。colorパッケージがおかしい? \lipsum[1-10] \end{document}
\output というのが、ストライプ柄のために定義した出力ルーチンです。 \box255 から \box1 へと1行ずつ取り出して交互に色を付け(\XmasStripe)、それらを次々に連結していった \box0 を作り(各行を連結するときにボックスの深さをつぶすために \vskip-\dp1 している)、最後にその \box0 をページとして吐き出しています。 ページとして吐き出すためのプリミティブは \shipout です。

実行結果はこんな感じ。もちろんドキュメントオプションでフォントサイズを変えても行ごとに色付けされます。スクリーンショットも貼っておきます。

注意

ここに載せたのはおもちゃのプログラムなので、ノンブル付けといった他の処理はすべて無効になってしまいます。 「Underfull/Overfull \vbox」もてんこ盛りです。

解説のほうも、いかにも出力ルーチンが簡単なものっぽい書き方をしてますが、正直なところ、書いている本人もそれほどちゃんとは理解していません。 なにせ、TeX が \box255 としてページを組み立てる処理と非同期に深く連動している処理です。 少なくとも TeX ブックの第14章、第15章を完全に理解している必要があります。 なので、本来はなるべくいじらないほうがいいでしょう。

参考文献

David Salomon "Output Routines: Examples and Techniques. Part I: Introduction and Examples."

2014/12/08

「版管理+自動組版」イベントの雑感

2014年12月6日、「版管理+自動組版」と題した勉強会というかイベントいうか集まりを開きました。 「同じようなことをバラバラにやってる人たちの横のつながりのきっかけが欲しいな」という程度の軽い気持ちで声を上げてみたら、予想以上に興味を持ってくれる人が膨らんで、様々な背景の方から発表に手を上げていただき、当日も会場から次々と質問が飛び交うという、実に活発で刺激的ですごく楽しい集まりになりました。ありがとうございました!

発表内容や概要は、公開されている各スライドや、武藤さんによる素晴らしいまとめを見ていただくとして、ここには個人的な感想文を晒しておこうと思います。参加してないと文脈が分かりづらい内容なのはご了承ください。

発表では、自分のゆるい話のあと、 @amedama こと宮川さんに Grifletという書籍のビルドシステムを紹介していただきました。 出版に継続的インテグレーションを適用するというのは、自分たちでも割と前から取り組んでいたのですが、この間ずっと未解決になっていた課題として、自動組版の環境をプロジェクトごとに固定化したりチーム間で共有したりしたいという要望がありました。 VM を使うとこまでは過去の本の重刷対応なんかでやっているのですが、Dockerのような仮想コンテナが使えることには気づけてませんでした。 それに気づいたのが、実は宮川さんの PyCon 2014 のスライドで、まさかその話をこうして直接聞ける機会がくるとは想像もしていませんでした。 今回の宮川さんの発表では、これを商品として提供するサービスの可能性にまで突っ込まれていて、商用フォント会社とうまい具合にライセンス契約ができれば同人誌の制作をしたいような人にとっても現実的な価格で再販できるのではとか、商業出版をやっている身としてもサービスを享受するという方向で夢が広がる話が伺えました。

商業出版という観点では、清水川さんによるかなり具体的な書籍制作フローの事例紹介がありました。 清水川さんは、執筆者としてドキュメント制作フローを語ったと思うのですが、それは自分たち出版社の人間から見てもなんら特別なものではなく、本作りのフローで必要なことは執筆者にとっても編集者にとっても共通する部分が多いという至極当たり前の事実を追認しました(ただ、スプレッドシートでSIer風にコメントを管理するのはつらいかもw)。

もう一つ追認したのは、上流でうまく回っているソースの版管理と最終的な本との間にギャップというかブラックボックスがあると、改訂版などへの再利用性は極端に低下するという事実です。 最後の最後まで版管理っていうのは、技術的なだけでなく政治的に難しい面もあるので、せめて、内容の編集までは出版社側の人間も reST ソースで完全に終わらせて、あとはレイアウト的な問題しか絶対に直さない、くらいの割り切りが必要かもしれません(レイアウト制作時に見つかった致命的な内容の問題が見つかったら必ずソースにも反映するようにする、といった抜け道は用意しておくとして)。

一方で、再利用性っていうのが「版管理+自動組版」をやりたい人にとって大前提の金科玉条になってもいけないと思いました。 そもそも何をどの程度に再利用可能にしたいのかは最終成果物によって大きく変わるはずで、必然的に、版管理する対象とか粒度、さらには制作システムのフローもまちまちにならざるを得ないでしょう。 で、そういう「共通化しにくさ」っていうのの具体例が、 taison さんの発表に出てきた 3つの事例かもなあと勝手に解釈していました。 taison さんの発表は、「どれも似たようなことをしているのに担保したいことが違って標準化できてない」という話をはじめ、あるある感で胸が痛くいっぱいになりました。

ところで、 taison さんのシステムで XML アプリケーションを固定していないのは、自分の経験としては正解だと感じてます。 DTD や Schema を用意するということは、梯子の下の表現レイヤを限定するということです。 もし、表現レイヤは決め打ちでいいというなら、わざわざ山かっこなんか使わず、人間にやさしい(⊃入力しやすい)マークアップを用意したほうがハッピーでしょう。 しかし、 XML という仕組みそのものには、文法的な一貫性を提供しつつ、ユーザが都合に合わせてマークアップを拡張できるという特徴があります。 表現レイヤを制限せず、なおかつ文法的に破たんしてないマークアップを提供したかったら、整形式であることだけを要求した XML を使うというのは割と理に適っているはずです。

マークアップを拡張することについては、小宮さんからも、拡張によるマークアップの方言化の是非という視点での発表がありました。 たとえば Sphinx でも、ベースとなるマークアップの reST を機能的に拡張するための API があって、それはすごく強力(なにぜPythonのフル機能がマークアップとして利用できるようになる!)なんだけど、それらの拡張が使える環境と使えない環境が生まれてしまう、さてどうしよう、という話だったと思います。 自分の知ってる TeX 界隈も、CTAN というオンラインのライブラリのおかげでマクロやパッケージの管理では比較的楽ができてますが、そもそもエンジンや PDF 生成機能がユーザから透過的とは言えない現状があります。 実際、 宮川さんの発表でも、LaTeX で Noto Sans を使いたかったけど TeX Live のエッジ(にしか入ってない dvipdfmx ですね)が必要で、システム全体を更新するわけにもいかないから、必要な Debian パッケージを入れた Docker コンテナを使ったという話がありました。 そんな風に環境ごとシステムの中の人が管理するという手はあるとはいえ、どうしたってユーザの利便性とか表現レイヤの自由度は下がるわけで、痛し痒しな気もします。

ひょっとすると、表現レイヤの自由度をあまり捨てずにマークアップの拡張による方言化を吸収できるかもしれない、そんな自動組版の最終兵器が、村上さんのプレゼンで紹介された CSS 組版なのかもしれません。 そこまで期待していいのかっていう内省もないではないんですが、技術的には夢の最終兵器としての可能性は間違いなく持ってるし、末端のユーザが実用できる日も遠くはない気がしています(実際、アンテナハウスの実装をはじめ、ちゃんと動いているものはあるわけで)。 むしろ心配なのは、 Web の世界では直接必要とされていない技術なので、ブラウザというプラットフォームで継続的に仕様と実装の開発が進むかどうかです。 TeX ユーザの集い2014 でも川端さんによるルビの標準化の話がありましたが、あれもお金がネックになって次の一手が出ないようだし、Web という巨大産業はどうやらそういう世界のようです。 そういう意味で、必要なのは銀の弾丸よりも金の弾丸なのでしょう……。 自動組版界隈、あんまりお金なさそうだし、どんな形で自分たちが利用できるようになるのかを含めて CSS 組版を盛り上げていくのに協力できればなと感じました。

最後の発表では、ここまで自動組版っぽい話題が多かったなかで、「同一性とは何か」まで考えさせられる版管理系のネタ を力武さんにぶつけていただきました。 図表の差分とれないのつらい、みたいな感想しか持ってなかったので、そもそも図表みたいなものの差分を取りたいのかという問題提起に付いてくだけで精一杯でしたが、 「差分を取るだけでなく、差分に意味を与えて差分をメンテナンスしてくのが編集という営みだろう(意訳)」という話が個人的には刺さりました。 git rebase -i に相当することができない版管理ツールを使うのはやめようと思いました(小並感)。

図表の差分の話もさることながら、 MS Word の原稿をどう比較するかという話への会場からの食いつきがよかったのが意外でした。 あんまり実体をしらないので、表面的な差分をとるだけならツールもけっこうあるし難しくない気がしてたんですが、そうでもないんですね……。 正直なところ、Word に限らず、閉じた世界でならものすごい利便性を提供するツールって、そこに身を投げれば版管理+自動組版が完全に解決するような錯覚に陥ることがあります。 図表の版管理にしても、閉じた世界で便利な解決策を提供しているツールがありそうなものなので、この辺りは寡聞にして知らない系の話というだけかも。

会の終了後は、主催者が参加できないことから公式の懇親会は企画しなかったのですが、一部の野良懇親会で濃い話が続いたりもしたようで、横のつながりの一助としても多少は機能できたっぽくてよかったです。 こういう特殊な軸で人が集まるのはとても面白いということがわかったので、同じ形になるかどうかはわかりませんが、また何かできればと思っています。

2014/12/02

LaTeX で git のメタ情報を使うパッケージ

2014年現在、「自動組版のためのPDF生成器である」と豪語できるツールは、どうやらまだまだ (La)TeX だけという状況っぽいです(異論は認める宗教戦争前夜)。 しかし、自動組版に使えると豪語するからには、 git のメタデータくらいは気軽に出力できないと困ります。にもかかわらず、どうも CATN にはこれといった便利パッケージが見当たらない†1。その愚痴を TeX & LaTeX Advent Calendar 2014 の2日目の記事としてぶつけます。(1日は ZR さん、明日3日は doraTeX さんです。大物に挟まれてつらい)

ところで、今週末の12/6(土)は、自動組版とバージョン管理の可能性を模索しようという「版管理+自動組版」という集まりがあります。 ものすごく楽しみです。 閑話休題。

さっそくですが、 git のメタ情報を (La)TeX のドキュメント中で利用するためのパッケージとしては、 gitinfo2vc といったものがあるようです。 いずれも、たとえば自分のドキュメントに git のコミットハッシュの文字列を含めたかったら、\gitHash(gitinfo2) とか \GITHash(vc)といった引数なしのコマンドを自分のドキュメントの中で指定して使う仕組みです。分かりやすいですね。

しかし、ここで厄介なのが、これらのパッケージのインストールと設定です。 いずれも TeX Live 2014 に入っているので、プリアンブルで \usepackage するだけで使えると実にうれしいのですが、残念なことにそんな安直な使い方をさせてくれません。事前に外部のスクリプトを自分で設定する必要があります。

vc パッケージの場合は、毎回ドキュメントをコンパイルする前に vc-git.awk というスクリプトを実行し、vc.tex というメタ情報が入ったファイルを生成して、このファイルをドキュメントから \include するという仕組みになっています。 なので、このスクリプトを手動で実行したり、 Makefile に記述しておいたり、 \write18 を使って vc-git.awk をドキュメントのコンパイル時に実行したりする必要があります。公式のドキュメントで説明されているのは \write18 を使う方法です。

gitinfo2 のほうは、 git に特化しているだけあって少しだけ先進的で、 git のフックスクリプトの機能を利用する仕組みになっています。 具体的には、

  • 特定のファイルに git log HEAD を出力するだけのコマンドシェルスクリプトを、ユーザが自分の .git/hooks ディレクトリに設置する
  • その特定のファイルをドキュメント中で \input で読み込んでおく
という具合です。 でもこれだと、GitHub をポーリングして CI を回している環境ではちょっと使いにくい(コミットフックで出力される特定のファイルをバージョン管理化におけばいいのですが、なんかいやだ)。

仕方がないので、自分ではこれらのパッケージを使っていません。 代わりに、 git のコミットハッシュを \write18 で取得するだけという、超低機能な自前コマンドを使っています。 これをプリアンブルに仕込んでおけば、 git の短縮版コミットハッシュの文字列が \gitRevision という引数なしのコマンドで得られます。

\immediate\write18{git show HEAD --pretty="\@percentchar h" -s > revision.tmp}
\def\gitRevision{\input{revision.tmp}}

さらに、みようみまねで buildinfo.sty という LaTeX2e 用のパッケージにしてみました。

\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{buildinfo}[2014/12/06 provides git hash as a command]
\RequirePackage{etoolbox}
\RequirePackage{kvoptions}
\SetupKeyvalOptions{family=buildinfo, prefix=buildinfo@}
\DeclareBoolOption{long}
\ProcessKeyvalOptions*
\immediate\write18{date +\@percentchar c  > build.tmp}
\ifbool{buildinfo@long}
  {\immediate\write18{git show HEAD --pretty="\@percentchar H" -s > revision.tmp}}
  {\immediate\write18{git show HEAD --pretty="\@percentchar h" -s > revision.tmp}}
\newcommand{\buildDate}{\input{build.tmp}}
\newcommand{\gitRevision}{\input{revision.tmp}}

\usepackage{buildinfo} すれば、 短縮版のコミットハッシュ文字列に展開される \gitRevision コマンド(\usepackage[long]{buildinfo} とすれば40文字になる)と、 コマンド実行時の日時の文字列に展開される \buildDate コマンドが使えるようになります。


†1 思い返せば、Subversionを使っていたころにも、 svninfo というパッケージでファイル単位でしかメタ情報を扱えないことに苦慮していたことがありました。(いまや完全に git に移行したので、この問題は個人的には原理的になくなりましたが。)