SATySFiでD論を書いた話

この記事は[SATySFi Advent Calendar](https://adventar.org/calendars/3929)13日目の記事です。前の記事は[@na4zagin3](https://qiita.com/na4zagin3)さんの「[素敵なライブラリをSatyrographosで配布しよう！](https://qiita.com/na4zagin3/items/b392f5d522f9bcc0493b)」で次は[@zptmtr](https://qiita.com/zptmtr)さんの「[SATySFiの組版結果をテストする](https://qiita.com/zptmtr/items/59b485c47edfbca67c60)」です。本記事では物理系の大学院生である筆者がD論をSATySFiで書いた話について紹介します。

#利用したもの
すでにgfngfnさんの修論用のクラスファイル[cs-thesis](https://github.com/gfngfn/cs-thesis)で目次の作成や図の挿入などの基本的な機能は実装されているため、これをベースに各自調整し、足りない機能を追加していくことになります。自分の場合は余白やフォントの変更に加え、cs-thesisのコードを参考にして、LaTeXにおけるAppendixや式番号などの機能を追加しました。例えば、式番号をつける数式`\eqn-num`は

```
let-mutable num-eqn <- 0

let eqn-scheme ctx labelopt =
  let () = num-eqn <- !num-eqn + 1 in
  let s-num = arabic !num-chapter ^ `.` ^ arabic !num-eqn in
  let () =
    match labelopt with
    | Some(label) -> register-cross-reference (`eqn:` ^ label ^ `:num`) s-num
    | None        -> ()
  in
    let it-num = embed-string (`(` ^ s-num ^ `)`) in
    it-num

let-inline ctx \eqn-num ?:labelopt inner =
  let num = eqn-scheme ctx labelopt in
  read-inline ctx {\eqn ?:(num) (inner);}
```
といった具合で追加できます。今回のD論で用いたクラスファイルについては近いうちに公開する予定です。
以下ではその他に実装する必要があったものなどについて紹介します。

#参考文献リスト
D論ともなると(分野にもよるとは思いますが)参考文献の数は膨大となり、`.bib`ファイルのような外部ファイルを利用し、ハイフネーションやコンマ区切り、引用文献の並び替えなどをうまいことやってくれるような、BibTeX相当の機能が必須となってきます。SATySFiでD論を書こうかなと漠然と考えていた9月頃に調べた限りではこのようなパッケージがなかったため、SATySFiの練習がてらに[bib-satysfi](https://github.com/shoudai/bib-satysfi)を実装しました[^1]。このパッケージはモジュール化などのコードの整備をしていない部分があるのでそのうちアップデートする予定ですが、通常使用する分には現状のままでも特に問題なさそうです[^2]。SATySFiから`.bib`ファイルのデータを直接読み込む手段がなさそうだったので、`.bib`ファイルを`.satyh`ファイルに変換するPythonスクリプトも一緒に置いてあります[^3]。ただし、日本語文献は想定されていないのでうまく変換できません。
使い方としては、まず`.bib`ファイルを`.satyh`に変換します。SATySFiの本文中では(括弧が多くて煩わしいですが)、

```
\cite([`hoge`;`fuga`]); % -> [1,2]
\cite([`hoge`;`fuga`;`piyo`]); % -> [1-3]
```
などのように引用し、文献リストは

```
+references(bibliography);
```
で出力します。なお、`.bib`からの自動変換が難しいものについては、数が多くないのであれば別の.satyhファイルを手動でを作っておいて

```
bibliography = (List.append bibliography user-bibliography);
```
のようにリストを結合すればなんとかなります。
ところで実装するにあたっていかにも`cross-reference`が使えそうな気がしていたのですが、`cross-reference`にはkeyとvalueに登録できるのが`string`のみという制限があります。本文で出てきた順番に1から番号を割り振って並び替えるという用途に合わなかったため、今回は`let-mutable`のみを使って実装しています[^4]。
[^1]: 実装するにあたり、学部生の頃にすごいH本などで関数型言語に触れたことがあったのがかなり役に立った気がします。
[^2]: 100以上引用してとりあえず問題なく使えています。
[^3]: ウムラウトなどの特殊文字の変換が面倒でそんなに数が多いわけでもなかったので、今の所は元ファイル自体を手動で直しています。簡単にやれる方法があったら教えていただけるとありがたいです。また、論文のタイトルを文献リストに表示することも想定していないのでタイトルの数式のエスケープはかなり適当です。
[^4]: この辺の話は[進捗大陸06](https://shinchokutairiku.booth.pm/items/1574189)が参考になりました。

#数式コマンド
物理でよく使うような、LaTeXにおける`\hat`や`\dot`、 `\braket`などは用意されていないので自分でコマンド定義を書く必要がありました。実装するにあたって必要なことはSATySFi Bookにだいたい書いてありますが、よく使うパターンについてここでまとめておきます。個人的な見解ですが、コマンド定義で重要なプリミティブは

```
embed-math : context -> math -> inline-boxes
text-in-math: math-class -> (context -> inline-boxes) -> math
math-paren: paren -> paren -> math -> math
```
あたりだと思います。特に`embed-math`と`text-in-math`を使うことで、`math型`と`inline-boxes型`を相互に変換するというのは常套手段で以下でも紹介します。この記事で紹介しているものを含め、筆者が実装した数式コマンドもクラスファイルとまとめてそのうち公開する予定です。

##文字や記号の上に描画
`\hbar`や`\hat`などがこれにあたります。これは文字や記号を`embed-math`で`inline-boxes`にしてから`inline-graphics`を用いて上から描画することで実現することができます。例えば、`\hat`は以下のように実装できます。

```
let-math \hat inner = 
  let hat ctx = 
    let ib = embed-math ctx inner in
    let (w, h, d) = get-natural-metrics ib in
    let thickness = 0.4pt in
    let color = Color.black in
    inline-graphics w h d (fun (x, y) -> 
    [
      stroke thickness color
        (start-path (x +' w *' 0.35, y +' h *' 1.2)
          |> line-to (x +' w *' 0.6, y +' h *' 1.4)
          |> line-to (x +' w *' 0.85, y +' h *' 1.2)
          |> terminate-path);
      draw-text (x, y) ib
    ])
  in
  text-in-math MathOp hat
```
`embed-math`でmath型の`inner`をinline-boxes型に変換してハット部分の描画を行い、最終的には`text-in-math`でmathに戻しています。なお、`\hbar`や`\AA`などの特殊文字に関しては同様に定義することも可能ですが、フォントが存在する場合はそのまま直接本文に入力したり、

```
let-math = math-char MathOrd (string-unexplode [8463])
let-inline ctx \AA = read-inline ctx {Å}
```
などとしてコマンド定義することもできます。

##括弧で囲う
こちらは`math-paren`を使います。例えば、物理でよく使う`\ket`や`\braket`は

```
let-math \ket = math-paren Math.abs-left (Math.angle-right 0.5pt)
let-math \braket = 
  math-paren-with-middle (Math.angle-left 0.5pt) (Math.angle-right 0.5pt) Math.abs-right
```
のように実装することができます。微分における代入を表す縦線など、片側のみに伸縮する"括弧"が欲しい場合は"空の括弧"`Math.empty-paren`を使います。

```
let-math \eval-bar = math-paren Math.empty-paren Math.abs-right
```
また、行列組版用のパッケージ[satysfi-matrix](https://nekketsuuu.github.io/entries/2018/10/12/satysfi-matrix.html)やmath.satyhの`\cases`などのように、`tabular`で作ったinline-boxesを`text-in-math`でmathに戻すことで表を様々な括弧で囲うこともできます。

##文中数式
物理系の論文なので文中数式は必須ですが、分数やルート、下付き上付き文字、伸縮する括弧などが組み合わさると縦方向に際限なく伸びてかなり不格好になってしまう上に、行間の設定によっては上下の行と接触してしまうこともあるようです。文中数式の分数や括弧は`\frac`や`\paren`ではなく単に`a/b`や`\(a\)`[^5]などを使って回避しました。ルートについては

```
let-math \smsqrt inner = 
  let smsqrt ctx = 
    let size = get-font-size ctx in
    let ctx-small = ctx |> set-font-size (size *' 0.85) in
    embed-math ctx-small ${\sqrt{#inner}} in
  text-in-math MathOrd smsqrt
```
のように中身のフォントサイズを小さくする`\smsqrt`を定義しました。`\sqrt`などに渡す数式そのものを小さくするコマンドを定義してもいいかもしれません。ここでわざわざ`embed-math`と`text-in-math`を通しているのは、math型ではcontextが変更できない[^6]ために一度inline-boxesを通す必要があるからです。以上でどうにもならないような複雑な数式に関してはそもそも文中数式ではなく別行立て数式にしてしまった方が無難な気がします。
[^5]: 数式中では'('と')'にはエスケープが必要。
[^6]: SATySFi Bookによると「math 型の値はインラインテキストに近いもので， 単に数式の “構造” を保持しているだけ」とのこと。

#印刷
提出締め切りの週になってD論のpdfを印刷しようとしたら、なぜかプリンタ側でエラーが出て詰みかけました。どうもMacOSのプレビューからSATySFiで生成したpdfを印刷しようとすると(少なくとも弊研究室で使っているプリンタでは)エラーがでるようで、Adobe Acrobat Readerからだと問題なく印刷できました。これに関しては理由がイマイチわかっていないのですが、締め切り直前は印刷がうまくいかないなどのトラブルは起きがちなので前もって確認しておきましょう。

#最後に
この記事では筆者がSATySFiでD論を書いたときに利用したものや、新たに実装した機能などについて紹介しました。SATySFiはまだ歴史が浅いこともあり、必要なものがまだ実装されていないケースが多く、(必要なことはだいたいSATySFi Bookに書いてあるものの)インターネット上の情報が少ないという点が大変でした。一方、SATySFiには他人のコマンド定義が比較的に容易に読めるという利点があり、[SATySFi本体のパッケージ群](https://github.com/gfngfn/SATySFi/tree/master/lib-satysfi/dist/packages)を参考にするとうまくいくことが多かったです。この記事が今後SATySFiで学位論文やレポートなどを書く人々の参考になれば幸いです。