LaTeX でアンチゴチしたい

<!-- # LaTeX でアンチゴチしたい -->

アンチック体のひらがな・カタカナ、ゴシック体の漢字の組み合わせになっている混植を「アンチゴチ」と呼びます。アンチゴチは漫画の吹き出し内でよく利用されています。

多くの場合、アンチゴチに対応したフォントを利用することでアンチゴチを実現します。

本記事では、LaTeX を使って仮名ならば明朝体、漢字ならばゴシック体になるようなコマンドを提供する antigoti パッケージを作って対応してみようという試みです。

## 基本的な考え方

例えば次のような文章があるとします。

```latex
ひらがな漢字カタカナ
```

` ひらがな ` と ` カタカナ ` は明朝体、` 漢字 ` はゴシック体を当てるには、以下のようになっていれば良いです。（見やすさのため改行しています）

```latex
\begingroup \mcfamily ひらがな\endgroup
\begingroup \gtfamily 漢字\endgroup
\begingroup \mcfamily カタカナ\endgroup
```

これを自動的にやってしまおうと言うのがパッケージの目標です。

ちなみに、文字種とそれぞれのフォント命令の対応は次のようにします。

| 文字種                   | フォント命令 |
| ------------------------ | ------------ |
| ひらがな・カタカナ       | `\mcfamily`  |
| 漢字                     | `\gtfamily`  |
| ラテン文字・アラビア数字 | `\sffamily`  |
| その他                   | そのまま     |

## 実装

expl3 で実装しました。また、実装に正規表現を利用しているため、LaTeX エンジンは LuaLaTeX に限定します。

引数となる文字を以下の 4 つのタイプのいずれかに分類します。

- ひらがな・カタカナ (`[\x{3040}-\x{309F}\x{30A0}-\x{30FF}\x{FF61}-\x{FF9F}？！、。；：（）]`)
- 漢字 (`[\x{2E80}-\x{2FDF}々〇〻\x{3400}-\x{4DBF}\x{4E00}-\x{9FFF}\x{F900}-\x{FAFF}\x{20000}-\x{3FFFF}]`)
- ラテン文字・アラビア数字 (`[a-zA-Z0-9?!().,]`)
- その他（何かしらのコマンドを想定：e.g. `\LaTeX`）

具体的な処理の流れは次のように作りました。

1. 引数の文字を 1 文字取る（実際はトークンを 1 つ取る）
1. `\begingroup`＋対応するフォント命令＋文字を連結
1. 次の文字を 1 文字取る
1. それまでの文字のタイプと同じかどうかを比較
    - 前の文字タイプと同じタイプの文字が現れたとき
        1. 同じタイプが続くまで文字列として連結
    - 前の文字タイプと異なるタイプの文字が現れたとき
        1. それまでの文字列＋`\endgroup` を連結
        1. 続いて `\begingroup`＋対応するフォント命令＋文字を連結
1. 3 に戻る（以下引数の文字が無くなるまでループ）
1. 完成した文字列を出力

これに加えて、実際に完成したものにはパッケージオプションやコマンドオプションを構成できるようにしています。

## できたもの

そんなこんなで antigoti パッケージが完成しました。

https://github.com/yarakos95/antigoti

使い方は、プリアンブルで `\usepackage{antigoti}` して、アンチゴチにしたい文章を `\antigoti` 内に入れるだけです。ただし、文章内には LaTeX コマンドが入っていないものとします。

実際に使ってみると次のようになります。

```latex
\documentclass{jlreq}
\usepackage{antigoti}
\begin{document}

\antigoti{アンチゴチとは、アンチック体の仮名とゴシック体の漢字を組み合わせた混植です。}

\end{document}
```

![antigoti.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/590705/431ad6e7-7260-e8b9-a161-c6c85b1570ed.png)

そのままだとフォントが貧弱でおかしな感じがするので、luatexja-preset（`deluxe` オプション付き）と antigoti の `bf` オプションを使ってみましょう。

```latex
\documentclass{jlreq}
\usepackage[deluxe]{luatexja-preset}
\usepackage[bf]{antigoti}
\begin{document}

\antigoti{アンチゴチとは、アンチック体の仮名とゴシック体の漢字を組み合わせた混植です。}

\end{document}
```

![antigoti-bf.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/590705/9aaef8e4-c8a4-b0f5-748f-9cc863687ab3.png)

と言うことで、LaTeX でいい感じにアンチゴチできました！

## 既知の問題

:::note info
この節は expl3 および TeX 言語の知識が必要です。
:::

この antigoti パッケージは 2 点の問題を抱えています。

- item が 2 つ以上になるコマンドはそのまま利用できない
- `\antigoti` 内で利用されたコマンドから展開される文字にはアンチゴチのためのフォント命令が適用されない

まず、item が 2 つ以上になるコマンドはダメです。

例えば、`\ref{label_name}` は item が 2 つです。そのため、これを `\antigoti` の中に入れるとエラーになります。

もしも `\antigoti` 内に含みたい場合は `{}` で囲ってください。囲うことで item は 1 つと見なされるので、処理できるようになります。ただし、中身を見ないので期待したフォントが与えられるわけではありません。

次に、コマンド内で展開された文字に当てるフォントはアンチゴチになりません。

例えば、次のようにすると明らかです。

```latex
\newcommand{\test}{ひらがなと漢字}
\antigoti{\test}
%% 仮名・漢字どちらも同じフォント命令の影響を受ける
```

これは、事前に `\test` が展開されないためです。これはパッケージ側で対応しなければ解決しない問題です。
ただ、どのように構成すれば対応できるのか不明だったためそのままになっています。

## 余談

文字種に応じてフォント命令を与えてアンチゴチ混植を実現するパッケージを作成してみました。

使いどころはあまりなさそうですが、興味のある人は利用してみてください。

### expl3 メモ

トークン列を扱う l3tl モジュールには l3seq モジュールにおける `\seq_pop_left`/`\seq_pop_right` のようなトークンリストの最左・最右のトークンを取り出すような関数はありません。

```latex
%% \l_tmpa_tl の最左のトークンを取り出して \l_tmpb_tl にセットする
%% こんな関数は存在しない
\tl_pop_left:NN \l_tmpb_tl \l_tmpa_tl
```

そのため、antigoti パッケージでは `\tl_range` で i 番目の item を取り出し、`\tl_set` でその item をセットするように構成しています。（トークンではなく item であることに注意）

```latex
%% \l_tmpa_tl から i 番目の item を取り出して \l_tmpb_tl にセットする
\tl_set:Nn \l_tmpb_tl { \tl_range:Nnn \l_tmpa_tl { i } { i } }
```

加えて、左から順に取り出す操作を繰り返したい場合は、`\int_do_until` でループ処理をして `\tl_range` にカウンタを入れていけば良いです。

```latex
%% \l_tmpa_tl から左から順に 1 文字ずつ 10 文字目まで取り出す
\int_set:Nn \l_tmpa_int { 0 }
\int_do_until:nn { \l_tmpa_int = 10 }
{
  \int_incr:N \l_tmpa_int
  \tl_set:Nf \l_tmpb_tl { \tl_range:Nnn \l_tmpa_tl { \int_use:N \l_tmpa_int } { \int_use:N \l_tmpa_int } }
  %% 取り出された 1 文字が入った \l_tmpb_tl に関する処理
  \tl_clear:N \l_tmpb_tl
}
```

antigoti パッケージではここまでに留めています。
もしも item からトークン列に代えて最左・最右から取り出したい場合、上と同じ方策を `\str_range` を使って文字列として取り出せば良いでしょう。