同人誌「代数的数を作る」ができるまで/PandocとかLaTeXの話

前の記事「技術書典5に初サークル参加した記録/前日まで」は、サークル参加と紙書籍配布を軸に色々書いたが、この記事ではコンテンツである「週刊 代数的実数を作る」からの「代数的数を作る」の流れと、執筆に使った技術について色々書く。

執筆に使った技術(Pandoc filter等)について気になるものがあれば、コメント欄に書き込んでもらえれば別途詳細な記事を書くかもしれない。

経緯とかはどうでもよくて執筆に使った技術を知りたい、という方は「技術書典へのサークル参加を決めるまで」は読み飛ばして欲しい。

目次

技術書典へのサークル参加を決めるまで

2017年10月に、「週刊 代数的実数を作る」 と題してWeb連載を始めた。

当時のブログ記事:「週刊 代数的実数を作る」創刊

「勉強しつつ執筆」という感じではあったが、連載を開始した時点ですでにある程度実装を進めていたため、行き当たりばったりではなくある程度計画を立てて勉強&執筆していた。

この連載は結局半年くらい続いて、全16回という形で完結した。

当時の記事にも書いたが、当初からなんらかのマネタイズ手段(投げ銭、書籍化)が欲しいと考えており、アフィリエイトやCoinHive(無断で採掘する方じゃなくてユーザーの操作が必要なやつ)を試したが、これらで得られる金額は微々たるものであり、やはり本命は書籍の販売であろうと思われた。

書籍化する場合に電子書籍か紙の書籍かという問題がある。電子書籍で売るというのが無難な気がするが、技術書典の盛り上がりを見ているとああいう場で物理的な本を売りたい、という気もしてくる。それに、自分が中心になって物理的な本を製作するという経験がこれまでなかった(サークルの部誌の記事を書いたり、学科有志が学園祭で配る冊子に関わったことはあった)ので、物理的な本を作る一通りの流れを自分で体験しておきたかった。

(ちなみに、半年ほど前にやっていた「LaTeX数式 to MathMLを考える」話は、この代数的数のやつをEPUBで電子書籍化することを目標にしていた)

ちなみに、技術書典4のサークル参加申し込み期間は2018年の1月で、連載の執筆中に「この調子で書いていけば4月の技術書典に出せるか…?」とも考えたが、連載も終わっていないのにサークル参加を申し込むのは危険だと感じて、サークル参加は見送った。ただし、技術書典5があるのなら是非サークル参加して書籍化したものを出したい、とも考えた。

6月下旬に技術書典5のアナウンスがあり、サークル参加申し込み期間が始まった。サークル名は「だめぽラボ」として申し込み、8月1日の当落発表で当選を知った。翌日くらいの早い段階でサークルカットを用意した。

当選が決まればあとは突っ走るしかない。未知のことが多すぎて不安に押しつぶされそうになるが、未来の自分がなんとかしてくれると信じて動くしかない。

ちなみに、Web連載を物理書籍化するにあたって予想外に苦しめられたのが「分量」である。Web連載や電子書籍はページ数の制約というものがないのでいくらでも書けてしまうし、ソースコードを全部載せてしまうことだってできる。しかし紙の書籍ではページ数が多いと

  • 分厚くなり、取り回しづらい
  • 印刷費が高くなり、辛い
  • 印刷所の締め切りが早まる
  • (紙書籍というか、LaTeXを経由する場合の問題だが)LaTeXの処理に時間がかかる
    • 1回の処理に1分、相互参照等を正しくするなら数分
    • この件に関する工夫は後述

などで苦しむことになる。

ソース原稿の形式:Markdown vs LaTeX

Web連載「週刊 代数的実数を作る」は、Web公開ということもあって、Markdownで書いていた。静的サイトジェネレーターのHakyllを通してPandocを使い、いくつかのフィルター(青空文庫風のルビ記法など)を自作した。

書籍化にあたっては、Markdown原稿からPandoc等でLaTeX/PDFに変換するか、それとも原稿をLaTeXで書き直すかを検討した(PDFを生成するにあたって筆者にはLaTeXが一番手慣れているので、それ以外の方法は考慮しない)が、

  • 分量が多く、全部書き直すのが大変
  • ソースコード片が至る所に出現する可能性のある文書としては、LaTeXにおいて\verb系コマンドがマクロ引数の中で使えないのは不安要素である(特定のコマンド引数で\verbが使えるか確認したり、使えない場合に回避策を取るのが面倒)

のでMarkdownを選択した。逆に、Markdownのデメリットとしては

  • ルビ、相互参照、定理環境や、その他まとまった文書を作るのに役立つ機能が標準的に用意されていない

というものがある。これらのデメリットは、Pandoc拡張や自作フィルターで克服していくことになる。

特に、Pandoc拡張を使うとMarkdown文書中にLaTeXのコマンドを書けるので、締め切りが近づくにつれて原稿がMarkdownとLaTeXのキメラになっていった。しかし、締め切りまでに本を完成させることこそが正義であり、ソースとなる原稿がMarkdownとして汚い/LaTeXとして汚いなどという問題は取るに足らないことである(もちろん、出来上がった文書の見た目が「正しい」場合の話だが)。

最終的に用意したPandocフィルターとしては、

  • 青空文庫《あおぞらぶんこ》風《ふう》のルビ記法《きほう》
  • <div>で書いた定理等をLaTeXの定理環境に変換するやつ
  • 図表を左右に並べて配置する
    • LaTeXのminipage環境を使う
    • PandocでRawBlockを使うとやたら空行が入って不都合だが、そこは小汚いトリックで乗り切る
  • 連載の個別記事間の相対リンクを削除する
    • Webへのリンクは脚注で書き出すようにしたが、相対リンクは単に削除されてほしいので
  • 埋め込んだSVG画像をPDFに変換する
    • そのSVGは元々LaTeX/TikZからPDF経由で変換されたものなので、なんかすごい無駄なことをしている気がするが…
    • 毎回変換していると遅いので、SVGファイルが変更されていなければPDFは作らない
  • Haskellコードをいい感じにする。具体的には、
    • 関数の型の -> には矢印 → を使う。関連:ソースコードの記号をいい感じに表示する
    • 名前が1文字だったり1文字+プライムな変数を、数式中のそれと同じように表示させる
    • コメント中に書いたLaTeX数式を、数式として印字させる。その他コメントで使ったHaddock風の記法(’〜’ や @〜@)を解釈する
    • ソースコードに関しては、最初はPandoc標準のやつ、そのうちlistingsに切り替えたが、最終的には、「自作Haskell字句解析器でソースコードを解析し、fancyvrb利用のLaTeXソースを書き出す」ようになった
    • (もっと詳しく知りたい人はコメント欄にその旨を書いてね)

などがある。これらのPandoc filterは処理対象の文書に特化したものが多い。ある程度の規模の文書をPandocで処理させるにはPandoc filterを書く技能が必須であろう。

筆者が自作したこれらのフィルターに興味があり、詳しく知りたいという方はコメント欄に書き込んで欲しい。気が向いたら解説記事を書くかもしれない。

今回の本の作成に関しては「ベースとなる原稿がMarkdownで書かれていて、量がめちゃくちゃ多い」「Pandocフィルターを量産したりTeXプログラミングを行うだけの腕力がある」という前提があったので、Markdown中にLaTeXコマンドを埋め込んだりPandocの制限をTeXプログラミングで乗り切ったりするような荒技を使ったが、LaTeXにある程度習熟した人が(HTML/EPUB化を考慮しなくて良い)本を書くのであれば、Markdownから変換なんてまどろっこしいことをせずに素直にLaTeXを直書きするべきである。

でかいLaTeX文書の処理の高速化

さっきもちらっと書いたが、260ページもある文書をLuaTeXで(1回)処理させると1分くらいかかる。相互参照等を正しくするためには複数回処理が必要で、そうすると処理全体として3分や4分かかる。

ただ、執筆中にちょっと出力を確認するだけであれば、複数回処理をして相互参照等を正しくする必要はないかもしれない。そういう場合は「本来複数回の処理が必要な場合でも、LaTeXの処理回数を1回で済ませる」ことにより、作業の高速化を計れる。

それでも、1回の処理時間が長い(1分くらい)のは辛い。この時間を削減するための(LaTeXの割と標準的な)方法として、

  • graphics/graphicxパッケージに draft オプションを渡して画像の埋め込みを抑制する
  • 文書をチャプターごとのファイルに分割して\include/\includeonlyを使う

などがある。前者はそのまんまなので、後者について詳しく説明する。

\includeonlyについて

\include/\includeonlyのメカニズムについてざっくり説明すると、\includeはLaTeX文書の一部を別ファイルに記述し、メインとなるファイル(親ファイル)から読み込む際に使うコマンドである。

\includeonlyは、\includeの際に一部のチャプターを省き、興味のある(現在編集中の)チャプターのみを処理対象とするコマンドである。一部のチャプターが省略されていても、ページ番号は本来のものが印字される。

\includeonlyを使うことによって、編集中の部分を確認するために文書全体を再処理する必要がなくなる。(\includeonlyを使用して生成されたPDFはあくまで確認用と割り切り、完成したPDFが欲しいのであれば\includeonlyを消した上で改めて処理する)

ただ、Pandocで処理する文書にこの技を適用するには問題がある。Pandocの吐くLaTeX文書は1つのファイルであって、\includeonlyの前提である「文書がチャプターごとのファイルに分割されており、\includeを使って親ファイルから小ファイルを読み込む」を満たさない。

そのため、「Pandocの生成したひとかたまりのLaTeXファイルから、\partや\chapterを認識しつつチャプターごとのファイル群に切り出す」スクリプトを書いた。例によってこのスクリプトも、特定の文書の書き方に依存したものであり、他の文書に適用するには手直しが必要となる類のものである。

チャプター分割スクリプトのおまけ機能として、「前回の処理時と比較して内容が変化したチャプターのリストを書き出す」機能をつけた。そのリストを\includeonlyに渡せば、いちいちチャプターを明示的に指定しなくても良い、「変更があったチャプターを検出して、該当するチャプターのみを含むPDFを作る」システムを構築できる。

このシステムを使った結果、トータルで1分ほどかかる文書処理が、変更したチャプターのみの確認目的(\includeonly使用)なら10秒程度で処理が終わるようになった。

なお、拙作LaTeXビルドツールClutTeXには、ツールのコマンドライン引数から \includeonly を指定できるオプションを実装している。

\includeの落とし穴

そんな感じのシステムを構築して\includeonlyを便利に使っていたが、どうも目次がおかしい。

「代数的数を作る」では、付録パートが始まる際に

\appendix
\phantomsection
\addcontentsline{toc}{part}{付録}

と書いて、目次に

付録
付録A ほげ
付録B ぴよ

という風に「付録」だけの行を出力している。

ところが、チャプターごとにファイルを分割してから処理させると、

付録A ほげ
付録
付録B ぴよ

という並びになってしまう。

色々調べて回った(.auxファイルも調べたし、\addcontentslineの実装も読んだ)ところ、これは既知の問題で、The LaTeX Companionにも記載があるらしい。

[texhax] \addcontentsline — line added to TOC appears in wrong place — ANSWER

結局、最終的なPDFで目次が正しければ良いので、執筆中にはおかしな目次を許容しつつチャプター分割(\includeを使う)を行い、最終的なPDFはチャプター分割する前(\includeを使わない)のLaTeXソースから生成することにした。

ページ数をケチる

openany

普通の書籍では章は奇数ページ起こし(横書きの場合は、右側のページから新しい章が始まる。前の章が右側のページで終わった場合は空白ページが挿入される)だが、同人誌の場合は少しでもページ数をケチるために偶数ページから新しい章が始まることを許したい。LaTeXの書籍向けクラスファイルでは、 openany クラスオプションによってこの設定を行える。

しかし、今回出した「代数的数を作る」では多少のページ数削減よりも本としての体裁を優先したので、 openany は指定しなかった。

あとは、\partを使うと2ページ持っていかれるのが辛い。工夫して直後のチャプター始まりと統合することも考えたが、そこまで手が回らなかった。まあ268ページあるうちの6ページ程度(\part3つ)は誤差だ(?)

ソースコードの行間を詰める

今回の本はソースコードの羅列がかなりの部分を占める。そのため、ソースコードの行間を詰めればページ数の削減につながる。

fancyvrb系環境では、  baselinestretch オプションで行間を調整できる。今回は baselinestretch=0.8 を指定した。

図表を横に並べる

幅がページ幅の半分以下な図表が連続して現れる場合、上下じゃなくて左右に配置すれば紙面の節約になる。

これはLaTeXではminipage環境を使うと実現できるが、PandocではLaTeX出力で図表からminipage環境を吐き出すようにはできない。ここはPandoc filterを使ったりTeXマクロで汚いことをやったりして対処した。

ページ数が奇数な章の内容を圧縮する

多分誰でもやることだと思う。

これは割と最終段階で適用する技だが、最終ページが奇数ページにちょっとだけはみ出している章があったら、内容を圧縮して章の最後のページを偶数ページにすれば(openanyを使用しないとして)2ページの節約になる。頑張ろう。

Pandocテンプレートのカスタマイズ/\frontmatterと\maketitle

本の前付(ページ番号がローマ数字な部分)のページ番号は普通はタイトルページから数えるようだ。つまり、LaTeXで言えば\frontmatterは\maketitleの前に書く。

しかしPandoc標準のLaTeXテンプレートでは\begin{document}と\maketitleの間には何も書けないようになっている。つまり、Pandocを使ってLaTeX経由で(前付のある)本を書くならテンプレートのカスタマイズは必須ということである。

文献処理

文献処理にはBibLaTeX/Biberを使用した。

文献リストの見せ方だが、単に1まとまりで列挙するだけでは味気ないので、ジャンル(代数の本、複素解析の本、Haskellの本、など)ごとに列挙するようにした。列挙の前に解説文も入れた。

文献の番号を文書全体での出現順ではなくジャンルの並びに従うようにするためには、biblatexパッケージの defernumbers オプションを使う。このオプションを使うと、「(文献参照も含めて)正しい出力を得るために必要なLaTeXの処理回数」が増える。

文献リストに関しては、英語の文献と日本語の文献で表示方法を変えられたら良かったのだが、そこまで手が回らなかった。


同人誌「代数的数を作る」ができるまで/PandocとかLaTeXの話」への2件のフィードバック

  1. ピンバック: LuaLaTeXでダウンロードカードを作った話 | 雑記帳

  2. ピンバック: 技術書典5に初サークル参加した記録/当日 | 雑記帳

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です