LoginSignup
14
10

ただしい高速LaTeX論

Last updated at Posted at 2023-12-24

概要

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

想定読者

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

  • Typstの導入コストに耐えられない人
  • ${\LaTeX}$の既存資産を活用したい人2
  • ${\LaTeX}$への偏愛がある人
  • 意味を孕んだ複雑性が好きな人

想定環境

  • TeX Live導入済
  • macOS3
  • Visual Studio CodeLaTeX Workshopを使って書いている4

ステップ一覧

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

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

latexmkの導入

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

インストール

TeX Liveに同梱なので省略。

latexmkrcの設定

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

#!/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の設定[雑多な記録]を参考とした。大まかな要素はそちらを参照していただきたい。以下は補足。

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

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

# 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使ってる人の気が知れない。
# 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>;にしておくべきだが、まぁそんなことは滅多にあるまい。

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

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

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

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

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

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

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

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

LaTeX Workshopの設定

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

{
    // ファイル保存時の実行「レシピ」
    "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環境を整える際のあれこれを参考にした。

用法

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

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

ぐらいか。

uplatexの使用

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

時間測定

後でやるかも?

mylatexformatの導入

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

latexmkrcの改造

latexmkrcの設定

# 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;

# 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対応)[むしゃくしゃしてやった,今は反省している日記]mylatexformat を用いてコンパイル時間を短縮しよう![TeX Alchemist Online]などを参考にした。以下解説。

# 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 fmtlatexPerlのサブルーチンとしてのfmtlatexを呼び出すことができる。引数がやたら多いのは後で使うため。

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

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

# 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対応)[むしゃくしゃしてやった,今は反省している日記]のそれを拡張した形になっている。条件分岐は以下の通り。

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

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

# 共有フォーマットファイルの確認・確保・破棄
{
    # 確認
    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チョットデキル人にかかればもっといい感じ8にできるんだろうが、個人用PCではそこまで問題にならないだろう。

# プリアンブル差分検知
{
    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によるプリアンブル依存の解消もやっている。

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

時間測定

後でやるかも?

subfilesの導入

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

使い方

例えば、subex.tex

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

subexsub.tex

\documentclass[./subex]{subfiles}

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

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

subex.png

subexsub.png

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

分割した LaTeX ファイルを subfiles を使ってコンパイルするを参考にした。

終わりに

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

追記記録

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

  1. マシンパワーで殴らないの意。

  2. 独自テンプレートだったり、オレオレスタイルファイルだったり

  3. OS固有の機能は使っていないので、移植は可能。

  4. CLIからの使用にも耐えるはずなので、枝葉末節。

  5. TeX Liveに含まれている。公式ページのようなものが見つからなかったので、ご存じの方情報提供お願いします。とりあえず作業過程が書かれているIssueを貼っておく。

  6. 余談だが、友人が全ての.texファイルを同一のフォルダに納めており、恐怖を禁じ得なかった。

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

  8. 複数フォーマット保持できたり、複数プロセスに対応したり。 2

14
10
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
10