カタカナを半角カタカナにしたい

<!-- # カタカナを半角カタカナにしたい -->

LaTeX でも半角カタカナを利用したいことはあるでしょう。しかし、半角カタカナを愚直に入力すると、コードが見づらかったり、濁点・半濁点の入力が面倒だったりします。
そのため、カタカナを半角カタカナに変換するコマンドがあると便利です。

そこで本記事では、すべてのカタカナを半角カタカナに変換する hankana パッケージを作って対応しようという試みです。

## `\aj半角` による半角カタカナ

実は、[japanese-otf][] パッケージや [luatexja-otf][] パッケージでは `\aj半角` があり、これを使うことでカタカナを半角カタカナに変換できます。
しかし、この方法では全てのカタカナを半角カタカナに変換出来ません。

[japanese-otf]: https://ctan.org/pkg/japanese-otf "CTAN: Package japanese-otf"
[luatexja-otf]: https://ctan.org/pkg/luatexja "CTAN: Package luatexja"

例えば、次のような文字を変換しようとすると、濁点・半濁点のあるカタカナや半角カタカナの無い小書きのカタカナは豆腐として出力されてしまいます。（原ノ味フォントを使用）

```latex
\aj半角{マンゴー}、\aj半角{パイナップル}、\aj半角{シークヮーサー}は沖縄の特産です。
```

![image-image-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/590705/bd80a4d7-d7af-40ec-b640-09cf6e01110c.png)

これは、対応するグリフがフォントから与えられていないことに起因します。そのため、うまく対応するフォントを探さない限り、この問題を解消する方法はありません。

そこで今回、すべてのカタカナが半角カタカナになる方法を提供するパッケージを作成してみました。

## hankana パッケージを作ってみた

https://github.com/yarakos95/hankana

[hankana-]: https://github.com/yarakos95/hankana/ "yarakos95/hankana"

hankana パッケージは事前にマッピングされた全角カタカナと半角カタカナの表を利用して変換を行います。

そのため、変換されないカタカナは存在しません。

## 使い方

使い方は簡単です。hankana パッケージを読み込むだけです。

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

すべての\hankana{カタカナ}は\hankana[hankaku-others]{半角}になる。

\end{document}
```

![image-image-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/590705/2a8f0500-392f-4825-afc0-ffbf08d5e656.png)

`\aj半角` では、次のようなカタカナは半角になりませんでした。

- 濁点・半濁点の付いたカタカナ
- 「ヮ」や「ㇻ」等の小書きのカタカナ

hankana パッケージでは、濁点・半濁点の付いたカタカナを半角カタカナ＋半角濁点・半濁点、小書きのカタカナを平体にした半角カタカナあるいは疑似的に半角幅にした小書きのカタカナに変換します。（小書きのカタカナには半角カタカナが与えられていないため、疑似的に対応しています）

```latex
\hankana{マンゴー}、\hankana{パイナップル}、\hankana{シークヮーサー}は沖縄の特産です。
```

![image-image-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/590705/4df6f0c1-dffa-456f-866e-97ee7be14916.png)

### オプション

パッケージオプションとコマンドオプションに共通したオプションがあります。

- `hankaku-kogaki` (`true`): 半角カタカナに対応が無い小書きのカタカナをどのように半角幅にするか

    - `true`: 半角カタカナを平体に変形
        - 平体にする割合は `kogaki-heitai` から調整可能
    - `false`: 小書きのカタカナを半角幅に変形
        - 小書きのカタカナ周囲はスペースがあるため、トリミング量を `kogaki-trim` から調整可能

- `hankaku-others` (`false`): カタカナ以外の文字を半角にするか

加えて、濁点・半濁点のあるカタカナのための 2 つの調整用オプションがあります。

- `dakuten-sep`: 濁点・半濁点と半角カタカナの間のスペース量
- `dakuten-after-sep`: 濁点・半濁点の左側のスペース量

`hankaku-kogaki` に関しては次のような結果を得ます。（「ッ」には半角カタカナがありますが、「ㇱ」や「ㇷ」には半角カタカナがありません）

![image-image-5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/590705/78a302c8-3cba-4325-b7af-214ffd6e9a19.png)

また、濁点・半濁点のスペースを調整するオプションを利用すると、次のような違いを得ます。

![image-image-6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/590705/9b3bb839-7a08-48ce-aac9-69a4ff0603fb.png)

## 実装

実装は、一文字ずつ文字列を取り出して、カタカナを対応する半角カタカナに置き替えているだけです。この対応するマップは [別ファイル][hankana.list] にしています。

[hankana.list]: https://github.com/yarakos95/hankana/hankana.list "hankana.list - yarakos95/hankana"

対応する文字に変換するには、以下のように `\str_case_e` を使った関数を使用しています。これによって、入力されたトークンリストに対応した文字が同じトークンリスト変数に返ってきます。

```latex
\cs_new_protected:Npn \__hankana_katakana_mapping:Nn #1#2 {
    \tl_set:Nx #1
    { \str_case_e:nn { \tl_use:N #1 } {#2} }
}
\cs_generate_variant:Nn \__hankana_katakana_mapping:Nn { NV }

%% マッピングリストは別ファイルからトークンリストとして導入される
\__hankana_katakana_mapping:NV \l_tmpb_tl \l__hankana_katakana_zenkaku_mapping_tl

%% \l_tmpb_tl の内容をシーケンスリストに右側から挿入される
\seq_put_right:NV \l__hankana_output_seq \l_tmpb_tl
```

また、小書きのカタカナや漢字は平体や長体に変形しています。これは、以下のように `\box_resize_to_wd_and_ht` を使って変形しています。

```latex
%% 長体に変形する関数
\cs_new:Npn \__hankana_cjk_trans_chotai:N #1 {
    \hbox_set:Nn \l_tmpb_box { \tl_use:N #1 }
    \box_resize_to_wd_and_ht:Nnn \l_tmpb_box { 0.5 \c__zw_unit_dim } { 1 \c__zw_unit_dim }
    \box_use_drop:N \l_tmpb_box
}
```

これをトークンリストとして呼び出して、シーケンスリストとしてダンプします。シーケンスリストは半角カタカナや平体に変形した小書きのカタカナ等が順にダンプされています。

```latex
%% \l_tmpb_tl が \l__hankana_cjk_trans_chotai によって書き変えられて \l_tmpb_tl にセットされる
\tl_set:Nx \l_tmpb_tl { \__hankana_cjk_trans_chotai:N \l_tmpb_tl }

%% \l_tmpb_tl の内容をシーケンスリストに右側から挿入される
\seq_put_right:NV \l__hankana_output_seq \l_tmpb_tl
```

これを `\seq_map_inline:Nn \l__hankana_output_seq { ##1 }` とすることで左から順に並べて出力しています。

## 余談

すべてのカタカナを半角カタカナに変換するパッケージを作ってみました。

フォントに依存せず、愚直に 1 文字ずつ半角カタカナに変換しているので、表示できない半角カタカナはありません。また、Unicode に存在しない小書きのカタカナの半角に関しては半角幅に調整することで、疑似的に半角カタカナとなるように調整しています。

hankana パッケージを使えば、オンドゥル語も簡単に出力できるようになりますね。

```latex
\hankana{オンドゥルルラギッタンディスカー！}
```

![image-image-4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/590705/d2536215-bb7b-45b0-99bc-483b2aaf68e9.png)

### expl3 メモ

hankana パッケージでは文字の置換を 1 文字ずつ `\str_case_e` で行っています。

例えば、促音の「っ」や拗音の「ゃ」「ゅ」「ょ」を普通の「つ」「や」「ゆ」「よ」に置き替える `\replaceKogaki` を構成するには、次のようにします。

```latex
\cs_new_protected:Npn \__sokuon_youon_mapping:Nn #1#2 {
    \tl_set:Nx #1
    { \str_case_e:nnF { \tl_use:N #1 } { #2 } { \tl_use:N #1 } }
}
\cs_generate_variant:Nn \__sokuon_youon_mapping:Nn { NV }

\tl_new:N \l__mappiing_tl
\tl_set:Nn \l__mapping_tl {
    { っ } { つ }
    { ゃ } { や }
    { ゅ } { ゆ }
    { ょ } { よ }
}

\tl_new:N \l__input_tl
\tl_new:N \l__output_tl
\NewDocumentCommand{ \replaceKogaki } { m } {
    \tl_set:Nn \l__input_tl { #1 }
    \int_step_inline:nnnn { 1 } { 1 } { \tl_count:N \l__input_tl }
    {
        \tl_set:Ne \l_tmpb_tl { \tl_range:Nnn \l__input_tl { ##1 } { ##1 } }
        \__sokuon_youon_mapping:NV \l_tmpb_tl \l__mapping_tl
        \tl_put_right:NV \l__output_tl \l_tmpb_tl
    }
    \tl_use:N \l__output_tl
}
```

この方法は、与えられた文字列を 1 文字ずつ置換して全体の文字列を出力します。

この他にも、`\regex_replace_case_all` を利用して置換もできます。こちらの方が簡単に書けます。ただし、置換後の文字に `\c_ampersand_str` 等が利用できません。（どうして……）

```latex
\tl_new:N \l__mapping_tl
\tl_set:Nn \l__mapping_tl
{
    { っ } { つ }
    { ゃ } { や }
    { ゅ } { ゆ }
    { ょ } { よ }
}

\tl_new:N \l__input_tl
\cs_generate_variant:Nn \regex_replace_case_all:nN { VN }
\NewDocumentCommand{ \replaceKogaki }{ m }{
    \tl_set:Nn \l__input_tl { #1 }
    \regex_replace_case_all:VN \l__mapping_tl \l__input_tl
    \tl_use:N \l__input_tl
}
```

実は、hankana パッケージを実装するときに `\regex_replace_case_all` を知らなかったので `\str_case_e` を利用しているという事情もあります。