Texlive 2024 / Windows 環境でmylatexformat ?

Qiita初投稿ですが，$\mathrm{p\LaTeX2\varepsilon}$ 関係の話になります。

私は主に **Texlive 2024** に **emath** パッケージを入れて使っていますが，
最近，空間図形の描画の関係で **TikZ** を使う機会があり，
プリアンブルの読み込みに時間が掛かることから
[正しい高速LaTeX論](https://event.phys.s.u-tokyo.ac.jp/physlab2024/advent-calendar/24-2/) などで紹介されていた **mylatexformat** の使用を試みました。

しかし，`.latexmkrc` に追加する内容として示されているものが，
恐らく bash など Linux 系の環境を想定したものとなっているようで，
結果，そのままでは動きませんでした。

とりあえず sed の代用として onigsed，
awk と head は Git をインストールした際に入ったもので代用，
ぐらいで上手く動くかと思っていたら，甘かった。

記録として，以下に挙げておく。

### .latexmkrc の内容（追加前）
~~~perl
#!/usr/bin/env perl
if ($^O eq 'MSWin32') {
# $latex = 'uplatex %O -kanji=utf8 -no-guess-input-enc -synctex=1 -interaction=nonstopmode %S';
# $latex = 'ptex2pdf.exe -l -ot -shell-escape -kanji=utf8 -synctex=0 -interaction=nonstopmode %S';
  $latex = 'platex %O -shell-escape -kanji=utf8 -no-guess-input-enc -synctex=0 -interaction=nonstopmode %S';
  $pdflatex = 'pdflatex %O -synctex=1 -interaction=nonstopmode %S';
  $lualatex = 'lualatex %O -synctex=1 -interaction=nonstopmode %S';
  $xelatex = 'xelatex %O -synctex=1 -interaction=nonstopmode %S';
  $biber = 'biber %O --bblencoding=utf8 -u -U --output_safechars %B';
  $bibtex = 'upbibtex %O %B';
  $makeindex = 'upmendex %O -o %D %S';
  $dvipdf = 'dvipdfmx %O -o %D %S';
  $dvips = 'dvips %O -z -f %S | convbkmk -u > %D';
  $ps2pdf = 'ps2pdf.exe %O %S %D';
  $pdf_mode = 3;
# if (-f 'C:/Program Files/SumatraPDF/SumatraPDF.exe') {
  if (-f '$ENV{LocalAppData}/SumatraPDF/SumatraPDF.exe') {
    $pdf_previewer = '"$ENV{LocalAppData}/Local/SumatraPDF/SumatraPDF.exe" -reuse-instance';
  } elsif (-f 'C:/Program Files/SumatraPDF/SumatraPDF.exe') {
    $pdf_previewer = '"C:/Program Files/SumatraPDF/SumatraPDF.exe" -reuse-instance';
  } elsif (-f 'C:/Program Files (x86)/SumatraPDF/SumatraPDF.exe') {
    $pdf_previewer = '"C:/Program Files (x86)/SumatraPDF/SumatraPDF.exe" -reuse-instance';
  } else {
    $pdf_previewer = 'texworks';
  }
} else {
  $latex = 'uplatex %O -synctex=1 -interaction=nonstopmode %S';
  $pdflatex = 'pdflatex %O -synctex=1 -interaction=nonstopmode %S';
  $lualatex = 'lualatex %O -synctex=1 -interaction=nonstopmode %S';
  $xelatex = 'xelatex %O -synctex=1 -interaction=nonstopmode %S';
  $biber = 'biber %O --bblencoding=utf8 -u -U --output_safechars %B';
  $bibtex = 'upbibtex %O %B';
  $makeindex = 'upmendex %O -o %D %S';
  $dvipdf = 'dvipdfmx %O -o %D %S';
  $dvips = 'dvips %O -z -f %S | convbkmk -u > %D';
  $ps2pdf = 'ps2pdf %O %S %D';
  $pdf_mode = 3;
  if ($^O eq 'darwin') {
    $pvc_view_file_via_temporary = 0;
    $pdf_previewer = 'open -ga /Applications/Skim.app';
  } else {
    $pdf_previewer = 'xdg-open';
  }
}
~~~

### .latexmkrc の内容（追加部分）
※[正しい高速LaTeX論](https://event.phys.s.u-tokyo.ac.jp/physlab2024/advent-calendar/24-2/) をベースに，手元の環境（Windows）用に一部修正
~~~perl

### fmtlatex (mylatexformat) の為の追加，以下。
# fmtlatexの呼び出し
  $latex = 'internal fmtlatex platex %Z %Y %A %S %R -shell-escape -kanji=utf8 -no-guess-input-enc -synctex=1 -file-line-error -halt-on-error -interaction=nonstopmode %O';

# 作業パス
# my $comdir=$ENV{HOME};
my $comdir=$ENV{TEMP};
my $comname=".latexmk";
# my $pwd=`pwd`;
my $pwd=`cd`;
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");
                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'";
    my $gethead = "\"C:\\Program Files\\Git\\usr\\bin\\awk.exe\" '!/%.*/{if (p) print}BEGIN{p=1}/\\\\\\\\endofdump/{p=0}/\\\\\\\\begin\\{document\\}/{p=0}'";
    my $comphead = "onigsed -e \"s/ *\$//g\" -e \"s/ *%.*\$//g\" -e \"/^^[ \t]*\$/d\"";

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

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

            # subfilesの利用を検出
            # 第1行が\documentclass[親ファイルパス]{subfiles}であればsubfiles使用とする
         #  my $mastername = `head -n 1 "$texname"`;
            my $mastername = `\"C:\\Program Files\\Git\\usr\\bin\\head.exe\" -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);
        # 比較
      # my $checkret = system("diff -Bb \"$preapath.tmp\" \"$preapath\"");
        my $checkret = system("\"C:\\Program Files\\Git\\usr\\bin\\diff.exe\" -Bb \"$preapath.tmp\" \"$preapath\"");
        return $checkret;
    }
    sub forget_preamble_change{
        my ($auxpath, $basename) = @_;
      # system("rm \"$auxpath$basename.$prea_ext.tmp\"");
        system("del \"$auxpath$basename.$prea_ext.tmp\"");
    }
    sub memorize_preamble_change{
        my ($auxpath, $basename) = @_;
      # system("mv \"$auxpath$basename.$prea_ext.tmp\" \"$auxpath$basename.$prea_ext\"");
        system(">nul move \"$auxpath$basename.$prea_ext.tmp\" \"$auxpath$basename.$prea_ext\"");
    }
}
~~~

変更前の内容がコメントとして残してあるため冗長ですが，
上手く動かないときにチェックするためそのまま上げています。
動作させる環境によって変更する必要があるのも，同じ辺りになると思います。

#### 注意点
- `$latex` に `-shell-escape` をつけているのは，latex から Perl を呼び出しているからです。必要ないなら，基本的につけるべきではないでしょう。
- 不具合があった場合にすぐ戻せるよう，追加前の「`$latex = ...`」の部分はそのまま残してあり，追加部分で改めて上書きしています。
- そろそろ uplatex に移行すべきなのでしょうが，未だに platex を使っています。
- subfiles を使う場合については未検証です。（機会があれば更新します。）

#### 参考（tex --> pdf変換の所要時間の例）
- 時間の掛かるファイル1個の変換（空間図形を3つ含む。TikZ 1つ，emath 2つ）
    - 従来方法で 2'55.89
    - mylatexformat 使って 2'01.66
- 細々とした93個のファイルの一括変換
    - 従来方法で 7'30.84（1個あたり 4.85秒）
    - mylatexformat 使って 5'29.83（1個あたり 3.55秒）

図形重いですねw

本来的には，何度もコンパイルしながら微調整が必要な場面において使用したり，
読み込みに時間の掛かるパッケージ等を読み込むときに使用すると効果があります。
図形などを含まない一般的な文書であれば恩恵は僅かです。

subfiles を使うときにはプリアンブルを共有するので特に効果がありそうですが，
最近存在を知ったばかりなんで試していません。


以上。