ただしい高速LaTeX論

## 概要

ただしく[^ただしく]${\LaTeX}$を高速化しましょう。

[^ただしく]: マシンパワーで殴らないの意。

## 想定読者

${\LaTeX}$を高速化したい人であって

- [`Typst`の導入コスト](https://qiita.com/key_271/items/8629980c4c1ff0e55f41)に耐えられない人
- ${\LaTeX}$の既存資産を活用したい人[^既存資産]
- ${\LaTeX}$への偏愛がある人
- 意味を孕んだ複雑性が好きな人

[^既存資産]:独自テンプレートだったり、オレオレスタイルファイルだったり

## 想定環境

- `TeX Live`導入済
- `macOS`[^macOS]
- `Visual Studio Code`で`LaTeX Workshop`を使って書いている[^workshop]

[^macOS]:OS固有の機能は使っていないので、移植は可能。
[^workshop]:CLIからの使用にも耐えるはずなので、枝葉末節。

## ステップ一覧

|問題点|対策|新規性|
|---|---|:-:|
|タイプセットを何度も何度も|`latexmk`の導入|x
|そもそもタイプセットが遅い|`uplatex`の使用|x
|パッケージ読み込みが重い|`mylatexformat`の導入|o
|`.tex`ファイルがでかい|`subfiles`の導入|x

第三列から明らかなように、当記事の主題は`mylatexformat`の適切な使用法であるが、周辺技術についても記述しておく。

## `latexmk`の導入

${\TeX}$のあるあるとして参照が切れないように複数回タイプセットがあるが、ちゃんと設定しないと変更がない中間ファイルも複数回生成される。`bibtex`やら`makeindex`やらが絡むとなおさらである。`latexmk`は中間ファイルを管理・監視して、必要最小限のタイプセットで`.pdf`ファイルを生成してくれる。

### インストール

`TeX Live`に同梱なので省略。

### `latexmkrc`の設定

探せばいくらでも転がっているが、例。

[latexmkの設定]: http://www2.yukawa.kyoto-u.ac.jp/~koudai.sugimoto/dokuwiki/doku.php?id=latex:latexmkの設定

```perl
#!/usr/bin/env perl

# カレントディレクトリ変更
$do_cd = 1;

# uplatexの呼び出し(後で変わる)
$pdf_mode = 3;
$latex = 'uplatex -synctex=1 -file-line-error -halt-on-error %O %S';
$dvipdf = 'dvipdfmx %O -o %D %S';
$max_repeat = 5;

# bibtex系
$bibtex_use=2;
$bibtex = 'upbibtex %O %S';
$biber = 'biber --bblencoding=utf8 -u -U --output_safechars %O %S';

# index
$makeindex = 'upmendex %O -o %D %S -s jpbase';

# ヴューワ
$dvi_previewer = "open %S";
$pdf_previewer = "open %S";

# 出力フォルダ指定
$out_dir = ".";
# 中間ファイルを別フォルダに隠しておける
$emulate_aux = 1;
$aux_dir = ".tex_intermediates";

# 中間ファイル登録
$clean_ext="$clean_ext run.xml";
```

[latexmkの設定[雑多な記録]][latexmkの設定]を参考とした。大まかな要素はそちらを参照していただきたい。以下は補足。

```perl
# カレントディレクトリ変更
$do_cd = 1;
```

`latexmk`がタイプセット対象の`.tex`ファイルの位置に`cd`してくれる。${\TeX}$は変な文字を嫌う(`~`など)ので、こうしておくとファイルパス中のダメ文字を拾われずに済む。
顕著な例として、`iCloud Drive`の実体は`/Users/<ユーザ名>/Library/Mobile Documents/com~apple~CloudDocs/`にあるため、これを入れないと確定で失敗する。

```perl
# uplatexの呼び出し
$pdf_mode = 3;
$latex = 'uplatex -synctex=1 -file-line-error -halt-on-error %O %S';
$dvipdf = 'dvipdfmx %O -o %D %S';
$max_repeat = 5;
```

`$latex`で指定したオプションは以下の意図がある。

- `-synctex=1`
    `SyncTeX`機能が有効になり、`.synctex.gz`ファイルが生成される。これがあると`.pdf`ヴューワと`.tex`ヴューワで対応する位置を相互に移れる。`LaTeX Workshop`下でも有効なのでつけておくと便利。
- `-file-line-error`
    エラーメッセージが`<ファイル名>:<行番号>:<エラー内容>`になる。`LaTeX Workshop`にエラーを認識してもらうのに必要。
- `-halt-on-error`
    エラーを吐いたら停止する。というか停止せず続行するってなんなんだろうか。`-interaction=nonstopmode`使ってる人の気が知れない。

```perl
# bibtex系
$bibtex_use=2;
$bibtex = 'upbibtex %O %S';
$biber = 'biber --bblencoding=utf8 -u -U --output_safechars %O %S';
```

`$bibtex_use=2;`を指定すると適宜`.bib`ファイルから適宜`.bbl`ファイルを生成する。つまり`.bbl`ファイルが中間ファイル扱いになる。自前の`.bbl`ファイルを使いたい人はファイルが消されないよう`$bibtex_use=<0 or 1 or 1.5>;`にしておくべきだが、まぁそんなことは滅多にあるまい。

```perl
# index
$makeindex = 'upmendex %O -o %D %S -s jpbase';
```

`upmendex`を用いる。これは[索引ツール比較[$\TeX$ Wiki]](https://texwiki.texjp.org/?索引作成ツール比較)によれば`uplatex`で利用可能な最も上位互換なツールのため。
`-s jpbase`で索引のフォーマットを`jpbase`[^jpbase]に指定している。本当は`.tex`ファイル側でフォーマットを指定できるべきだし、実際できるのだが、それをやるには`-shell-escape`というやや危ない橋を渡ることになるのでとりあえず一番振る舞いの良いこいつで固定。このあとやる`latexmkrc`改造と似たようなことをすれば変更可能になるはず。

[^jpbase]:`TeX  Live`に含まれている。公式ページのようなものが見つからなかったので、ご存じの方情報提供お願いします。とりあえず[作業過程が書かれているIssue](https://github.com/texjporg/mendex-doc/issues/11)を貼っておく。

```perl
# ヴューワ
$dvi_previewer = "open %S";
$pdf_previewer = "open %S";
```

`latexmk`に`-v`オプションを入れた時に使うヴューワを指定するが、`LaTeX Workshop`を使っているならそんな機会はまずないだろう。

```perl
# 出力フォルダ指定
$out_dir = ".";
# 中間ファイルを別フォルダに隠しておける
$emulate_aux = 1;
$aux_dir = ".tex_intermediates";
```

出力フォルダの`.`はカレントディレクトリを意味し、`cd`しているから要するに`.tex`ファイルのある位置を意味する。
中間ファイルの出力先を変更する機能は${\TeX}$系のツールには存在しないのだが、`latexmk`が適宜動かしてくれる。`.tex_intermediates`のように、隠しフォルダに入れておくとメインのフォルダがスッキリ[^スッキリ]して良い。

[^スッキリ]:余談だが、友人が全ての`.tex`ファイルを同一のフォルダに納めており、恐怖を禁じ得なかった。

```perl
# 中間ファイル登録
$clean_ext="$clean_ext run.xml";
```

`LaTeX Workshop`がプロセス監視の結果として`.run.xml`を生成するので、こいつを消去対象の拡張子に加えておく。

### `LaTeX Workshop`の設定

中間ファイルの管理を全部`latexmk`側に押し付けると、`LaTeX Workshop`の設定はこのぐらいでいい。

```json-doc
{
    // ファイル保存時の実行「レシピ」
    "latex-workshop.latex.recipe.default": "latexmk",
    "latex-workshop.latex.recipes": [
        {
            // latexmkを叩くだけのレシピ
            "name": "latexmk",
            "tools": ["latexmk"]
        }
    ],
    // レシピに使われるパーツ
    "latex-workshop.latex.tools": [
        {
            "name": "latexmk",
            "command": "latexmk",
            "args": [
                // "-time"で実行時間を表示してくれる。
                "-time",
                "%DOC%"
            ],
        }
    ],
    // あとはお好みで
    "latex-workshop.intellisense.package.enabled": true,
    "latex-workshop.latex.outDir": "",

    "latex-workshop.synctex.afterBuild.enabled": true,
    "latex-workshop.view.pdf.viewer": "tab",
    "latex-workshop.latex.autoBuild.cleanAndRetry.enabled": false,
}
```

[VScodeでLaTex環境を整える際のあれこれ](https://zenn.dev/okaito/articles/5a9e24f4223a6c)を参考にした。

### 用法

これもいくらでも転がっているが、頻用するのは

```shell
latexmk <.texファイル名> # タイプセット
latexmk -c # 中間ファイルだけ削除
latexmk -C # 全出力ファイル削除(.pdfも消える)
```

ぐらいか。

## `uplatex`の使用

`lualatex`はスクリプトとかかけて便利ですが、普通に遅いので`uplatex`使いましょう。ただしさ[^correct]は速さを必ずしも意味しないので。

### 時間測定

後でやるかも？

[^correct]: 構成が適切であること。`Unicode`にデフォルト対応だったり、OS側のフォントを使えたりすることを指している。

## `mylatexformat`の導入

大規模パッケージを複数入れたりすると、タイプセットのオーバーヘッドがかなりの時間を占めるようになってくる。
実は${\TeX}$にはフォーマットファイルを作っておく機能(イニシャルモード)があるのだが、普通にやると`\begin{document}`以降の中身までフォーマット化してしまう。
`mylatexformat`パッケージは${\TeX}$のイニシャルモードをハックして、プリアンブル部分だけのフォーマットファイルを作成してくれる。

### `latexmkrc`の改造

 [`latexmkrc`の設定](#latexmkrcの設定)の

```perl
# uplatexの呼び出し
$pdf_mode = 3;
$latex = 'uplatex -synctex=1 -file-line-error -halt-on-error %O %S';
$dvipdf = 'dvipdfmx %O -o %D %S';
$max_repeat = 5;
```

を

```perl
# fmtlatexの呼び出し
$pdf_mode = 3;
$latex = 'internal fmtlatex uplatex %Z %Y %A %S %R -synctex=1 -file-line-error -halt-on-error %O';
$dvipdf = 'dvipdfmx %O -o %D %S';
$max_repeat = 5;

# 作業パス
my $comdir=$ENV{HOME};
my $comname=".latexmk";
my $pwd=`pwd`;
chomp $pwd;

# fmtlatex メインルーチン
{
    # 拡張子を登録
    $clean_ext="$clean_ext fmt";
    my $initial = 1;

    sub fmtlatex {
        # 引数読込
        my ($engine, $outpath, $auxpath, $basename, $texname, $jobname, @args) = @_;
        my $options = join(' ', @args);

        # 初回実行時
        if ($initial == 1){
            $initial = 0;
            # フォーマット生成フラグ
            my $flag = 0;
            print "fmtlatex: checking if the preamble changed...\n";
            if (&check_preamble_change($auxpath,$jobname,$texname) == 0){
                print "fmtlatex: the preamble is not changed.\n";
                print "fmtlatex: checking if the common fmt file is owned...\n";
                if (&check_com_owned("$pwd/$texname") == 0){
                    print "fmtlatex: the common fmt file is not owned.\n";
                    $flag = 1;
                }else{
                    print "fmtlatex: the common fmt file is owned.\n";
                }
            }else{
                print "fmtlatex: the preamble is changed.\n";
                $flag = 1;
            }
            if ($flag == 1){
                print "rewriting the common fmt file in ini mode...\n";
                # フォーマット生成
                my $iniret=Run_subst("$engine -ini $options -output-directory=\"$comdir\" -jobname=\"$comname\" \\\&$engine mylatexformat.ltx $texname");
                if($iniret == 0){
                    print "fmtlatex: the common fmt file rewrited. saving preamble...\n";
                    &memorize_preamble_change($auxpath,$jobname);
                    &hold_com("$pwd/$texname");
                }else{
                    print "fmtlatex: failed to rewrite the common fmt file.\n";
                    &forget_preamble_change($auxpath,$jobname);
                    &throw_com("$pwd/$texname");
                    return $iniret;
                }
            }else{
                print "keep the common fmt file.\n";
                &forget_preamble_change($auxpath,$jobname);
            }
        }
        print "fmtlatex: the common fmt file is ready, so running normal latex... \n";
        # 通常のタイプセット
        my $finalres = Run_subst("$engine -fmt \"$comdir/$comname\" $options $texname");
        return $finalres;
    }
}

# 共有フォーマットファイルの確認・確保・破棄
{
    # 確認
    sub check_com_owned(){
        my $path=$_[0];
        open(my $fh, "<", "$comdir/$comname.info");
        my $holder=<$fh>;
        close($fh);
        if($path eq $holder){
            return 1;
        }else{
            return 0;
        }
    }
    # 確保
    sub hold_com(){
        my $path=$_[0];
        open(my $fh, ">", "$comdir/$comname.info");
        print $fh "$path";
        close($fh);
    }
    # 破棄(生成失敗時用)
    sub throw_com(){
        open(my $fh, ">", "$comdir/$comname.info");
        print $fh "";
        close($fh);
    }
}

# プリアンブル差分検知
{
    my $prea_ext = "prea";
    $clean_ext="$clean_ext $prea_ext";

    # プリアンブル抽出用のコマンド(未改修)
    # \endofdumpまたは\begin{document}まで読み出して保存
    my $gethead = "awk '!/%.*/{if (p) print}BEGIN{p=1}/\\\\endofdump/{p=0}/\\\\begin\\{document\\}/{p=0}'";
    my $comphead = "sed -e 's/ *\$//g' -e 's/%.*\$//g'";

    sub check_preamble_change{
        my ($auxpath, $basename, $texname) = @_;
        my $preapath="$auxpath$basename.$prea_ext";
        # プリアンブル部の一時ファイルをクリア
        system("echo \"\" > \"$preapath.tmp\"");

        my $chain_flag=1;
        # subfilesによるプリアンブル依存が終わるまで続ける
        do{
            system("$gethead \"$texname\"|$comphead >> \"$preapath.tmp\"");
            system("echo \"\" >> \"$preapath.tmp\"");

            # subfilesの利用を検出
            # 第1行が\documentclass[親ファイルパス]{subfiles}であればsubfiles使用とする
            my $mastername = `head -n 1 "$texname"`;
            if ($mastername =~ /^ *\\documentclass\[.*\]\{subfiles\} *$/){
                $mastername =~ s/^ *\\documentclass\[//g;
                $mastername =~ s/\]\{subfiles\} *$//g;
            }else{
                $mastername = "";
            }
            chomp($mastername);
            # $masternameはsubfilesを利用していれば拡張子なしの親ファイルパスが入っている
            if ($mastername ne ""){
                $texname = "$mastername.tex";
            }else{
                $chain_flag=0;
            }
        }while($chain_flag == 1);

        ### 2024/05/24 追記 @utaoji 氏のリクエストを反映  ###
        # input先を読み込む
        # $preapath.tmpの中身を一行ずつ読む
        &process_input_files($preapath);

        sub process_input_files{
            my ($preapath) = @_;
            #プリアンブル読み込み制限（inputの循環回避用）
            my $loading_limit=1000;
            open(my $fh, '<', $preapath.".tmp") or die "Error: $!\n";
            print "Processing $preapath.tmp\n";
            my $i=0;
            while (my $line = <$fh>) {
                $i=$i+1;
                last if $i >= $loading_limit; 
                if ($line =~ /\\input\{([^}]*)\}/) {
                    my $inputname = $1;
                    $inputname =~ s/\} *$//g;
                    print "Found input directive: $inputname\n";
                    system("$gethead \"$inputname\"|$comphead >> \"$preapath.tmp\"");
                    system("echo \"\" >> \"$preapath.tmp\"");
                }
            }
        }
        ### 追記終わり ###

        # 比較
        my $checkret = system("diff -Bb \"$preapath.tmp\" \"$preapath\"");
        return $checkret;
    }
    sub forget_preamble_change{
        my ($auxpath, $basename) = @_;
        system("rm \"$auxpath$basename.$prea_ext.tmp\"");
    }
    sub memorize_preamble_change{
        my ($auxpath, $basename) = @_;
        system("mv \"$auxpath$basename.$prea_ext.tmp\" \"$auxpath$basename.$prea_ext\"");
    }
}
```

に置換。[mylatexformatでLaTeX高速化（latexmk・Overleaf対応）[むしゃくしゃしてやった，今は反省している日記]](https://tasusu.hatenablog.com/entry/2022/04/07/044634)や[mylatexformat を用いてコンパイル時間を短縮しよう！[TeX Alchemist Online]](https://doratex.hatenablog.jp/entry/20211206/1638749451)などを参考にした。以下解説。

```perl
# fmtlatexの呼び出し
$pdf_mode = 3;
$latex = 'internal fmtlatex uplatex %Z %Y %A %S %R -synctex=1 -file-line-error -halt-on-error %O';
$dvipdf = 'dvipdfmx %O -o %D %S';
$max_repeat = 5;
```

`internal fmtlatex`で`Perl`のサブルーチンとしての`fmtlatex`を呼び出すことができる。引数がやたら多いのは後で使うため。

```perl
# 作業パス
my $comdir=$ENV{HOME};
my $comname=".latexmk";
my $pwd=`pwd`;
chomp $pwd;
```

フォーマットファイルおよび関連ファイルを`~/.latexmk.<拡張子>`で生成すると設定。これはフォーマットファイルのサイズが数十MBぐらいあり、そのままだと各`.tex`ファイルにつき数十MBが消費されるため。`.tex`ファイルの編集作業の局所性を活かして、フォーマットファイルをキャッシュとしてだけ保持する形にして節約している。またクラウドドライブに巨大な書き込みをしないためでもある。
以降、このホームディレクトリ直下に生成されるフォーマットファイルを**共通フォーマットファイル**と呼ぶことにする。

```perl
# fmtlatex メインルーチン
{
    # 拡張子を登録
    $clean_ext="$clean_ext fmt";
    my $initial = 1;

    sub fmtlatex {
        # 引数読込
        my ($engine, $outpath, $auxpath, $basename, $texname, $jobname, @args) = @_;
        my $options = join(' ', @args);

        # 初回実行時
        if ($initial == 1){
            $initial = 0;
            # フォーマット生成フラグ
            my $flag = 0;
            print "fmtlatex: checking if the preamble changed...\n";
            if (&check_preamble_change($auxpath,$jobname,$texname) == 0){
                print "fmtlatex: the preamble is not changed.\n";
                print "fmtlatex: checking if the common fmt file is owned...\n";
                if (&check_com_owned("$pwd/$texname") == 0){
                    print "fmtlatex: the common fmt file is not owned.\n";
                    $flag = 1;
                }else{
                    print "fmtlatex: the common fmt file is owned.\n";
                }
            }else{
                print "fmtlatex: the preamble is changed.\n";
                $flag = 1;
            }
            if ($flag == 1){
                print "rewriting the common fmt file in ini mode...\n";
                # フォーマット生成
                my $iniret=Run_subst("$engine -ini $options -output-directory=\"$comdir\" -jobname=\"$comname\" \\\&$engine mylatexformat.ltx $texname");
                if($iniret == 0){
                    print "fmtlatex: the common fmt file rewrited. saving preamble...\n";
                    &memorize_preamble_change($auxpath,$jobname);
                    &hold_com("$pwd/$texname");
                }else{
                    print "fmtlatex: failed to rewrite the common fmt file.\n";
                    &forget_preamble_change($auxpath,$jobname);
                    &throw_com("$pwd/$texname");
                    return $iniret;
                }
            }else{
                print "keep the common fmt file.\n";
                &forget_preamble_change($auxpath,$jobname);
            }
        }
        print "fmtlatex: the common fmt file is ready, so running normal latex... \n";
        # 通常のタイプセット
        my $finalres = Run_subst("$engine -fmt \"$comdir/$comname\" $options $texname");
        return $finalres;
    }
}
```

メインルーチンは専ら[mylatexformatでLaTeX高速化（latexmk・Overleaf対応）[むしゃくしゃしてやった，今は反省している日記]](https://tasusu.hatenablog.com/entry/2022/04/07/044634)のそれを拡張した形になっている。条件分岐は以下の通り。

- 初回タイプセットである
↓
    - プリアンブルが変更されていない
    ↓
        - 共通フォーマットファイルが維持されている
        →通常のタイプセット
        - 共通フォーマットファイルが維持されていない
        →フォーマット生成+通常のタイプセット
    - プリアンブルが変更されている
    →フォーマット生成+通常のタイプセット
- 初回タイプセットではない
→ 通常のタイプセット

これにより、真にフォーマットファイル生成が必要な時のみ生成することになり、ほとんどのタイプセットでプリアンブル部分のタイプセットを省略することになる。
全体のコードが中括弧`{}`で囲ってあるのは変数のスコープを切るため。グローバル空間に`$initial`が置かれてるのは怖すぎる。

```perl
# 共有フォーマットファイルの確認・確保・破棄
{
    # 確認
    sub check_com_owned(){
        my $path=$_[0];
        open(my $fh, "<", "$comdir/$comname.info");
        my $holder=<$fh>;
        close($fh);
        if($path eq $holder){
            return 1;
        }else{
            return 0;
        }
    }
    # 確保
    sub hold_com(){
        my $path=$_[0];
        open(my $fh, ">", "$comdir/$comname.info");
        print $fh "$path";
        close($fh);
    }
    # 破棄(生成失敗時用)
    sub throw_com(){
        open(my $fh, ">", "$comdir/$comname.info");
        print $fh "";
        close($fh);
    }
}
```

`$comdir/$comname.info"`、すなわち`~/.latexmk.info`にはタイプセット対象の`.tex`ファイルのパスが書き込まれる。これを以って共通フォーマットファイルの生成元を識別する。多分`Perl`ﾁｮｯﾄﾃﾞｷﾙ人にかかればもっといい感じ[^いい感じ]にできるんだろうが、個人用PCではそこまで問題にならないだろう。

[^いい感じ]: 複数フォーマット保持できたり、複数プロセスに対応したり。

```perl
# プリアンブル差分検知
{
    my $prea_ext = "prea";
    $clean_ext="$clean_ext $prea_ext";

    # プリアンブル抽出用のコマンド(未改修)
    # \endofdumpまたは\begin{document}まで読み出して保存
    my $gethead = "awk '!/%.*/{if (p) print}BEGIN{p=1}/\\\\endofdump/{p=0}/\\\\begin\\{document\\}/{p=0}'";
    my $comphead = "sed -e 's/ *\$//g' -e 's/%.*\$//g'";

    sub check_preamble_change{
        my ($auxpath, $basename, $texname) = @_;
        my $preapath="$auxpath$basename.$prea_ext";
        # プリアンブル部の一時ファイルをクリア
        system("echo \"\" > \"$preapath.tmp\"");

        my $chain_flag=1;
        # subfilesによるプリアンブル依存が終わるまで続ける
        do{
            system("$gethead \"$texname\"|$comphead >> \"$preapath.tmp\"");
            system("echo \"\" >> \"$preapath.tmp\"");

            # subfilesの利用を検出
            # 第1行が\documentclass[親ファイルパス]{subfiles}であればsubfiles使用とする
            my $mastername = `head -n 1 "$texname"`;
            if ($mastername =~ /^ *\\documentclass\[.*\]\{subfiles\} *$/){
                $mastername =~ s/^ *\\documentclass\[//g;
                $mastername =~ s/\]\{subfiles\} *$//g;
            }else{
                $mastername = "";
            }
            chomp($mastername);
            # $masternameはsubfilesを利用していれば拡張子なしの親ファイルパスが入っている
            if ($mastername ne ""){
                $texname = "$mastername.tex";
            }else{
                $chain_flag=0;
            }
        }while($chain_flag == 1);

        ### 2024/05/24 追記 @utaoji 氏のリクエストを反映  ###
        # input先を読み込む
        # $preapath.tmpの中身を一行ずつ読む
        &process_input_files($preapath);

        sub process_input_files{
            my ($preapath) = @_;
            #プリアンブル読み込み制限（inputの循環回避用）
            my $loading_limit=1000;
            open(my $fh, '<', $preapath.".tmp") or die "Error: $!\n";
            print "Processing $preapath.tmp\n";
            my $i=0;
            while (my $line = <$fh>) {
                $i=$i+1;
                last if $i >= $loading_limit; 
                if ($line =~ /\\input\{([^}]*)\}/) {
                    my $inputname = $1;
                    $inputname =~ s/\} *$//g;
                    print "Found input directive: $inputname\n";
                    system("$gethead \"$inputname\"|$comphead >> \"$preapath.tmp\"");
                    system("echo \"\" >> \"$preapath.tmp\"");
                }
            }
        }
        ### 追記終わり ###

        # 比較
        my $checkret = system("diff -Bb \"$preapath.tmp\" \"$preapath\"");
        return $checkret;
    }
    sub forget_preamble_change{
        my ($auxpath, $basename) = @_;
        system("rm \"$auxpath$basename.$prea_ext.tmp\"");
    }
    sub memorize_preamble_change{
        my ($auxpath, $basename) = @_;
        system("mv \"$auxpath$basename.$prea_ext.tmp\" \"$auxpath$basename.$prea_ext\"");
    }
}
```

`.tex`ファイルからプリアンブルを抽出し、`<texファイル名(拡張子なし)>.prea.tmp`に書き出し。これを前回の結果である`<texファイル名(拡張子なし)>.prea`と比較して差分を検出する。ここで、この後導入する`subfiles`によるプリアンブル依存の解消もやっている。

かなーり場当たり的に拡張してきたので、もっといい感じ[^いい感じ]に確実にできるが、いかんせん余暇がない。余裕がある人はお好きにどうぞ。

### 時間測定

後でやるかも？

## `subfiles`の導入

ファイル分割パッケージにも色々あるが、プリアンブルを省略・共有できる`subfiles`を使うのが一般的な用法だと相性が良さそうに見える。

### 使い方

例えば、`subex.tex`を

```tex
\documentclass[uplatex,dvipdfmx,a4paper]{jsarticle}
\usepackage{subfiles}
\newcommand{\fermi}{フェルミオンのファミリーマート、フェルミーオーン}
\newcommand{\bose}{ボソンのローソン、ボーソン}
\begin{document}
    \section{メインファイル}
    \fermi
    \subfile{subexsub}
\end{document}
```

`subexsub.tex`を

```tex
\documentclass[./subex]{subfiles}

\begin{document}
    \section{サブファイル}
    \bose
\end{document}
```

にすると(`.tex`の拡張子はいらないことに注意)、それぞれこんな感じの`.pdf`ファイルが出力される。

![subex.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3629194/27c8b0ae-0c77-8e0c-17fe-41ada9d18747.png)

![subexsub.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3629194/c3423de1-f4f1-1d6c-7b21-0d1e36dfa420.png)

`subex.tex`で定義した`\bose`を`subexsub.tex`でも使えていることがわかる。

[分割した LaTeX ファイルを subfiles を使ってコンパイルする](https://qiita.com/sankichi92/items/1e113fcf6cc045eb64f7)を参考にした。

## 終わりに

良い子のみんなは初めから`Typst`使って幸せになろうね。

## 追記記録

2024/05/24: @utaoji 氏の編集リクエストを反映。`\input{}`先の更新を検知するようになりました。ありがとうございます。