"𝑰𝑵𝑻𝑬𝑹𝑵𝑬𝑻" のような装飾文字に変換するJavaScriptのライブラリを書いた

Web上では、アルファベット（ASCII文字）を `𝑰𝑵𝑻𝑬𝑹𝑵𝑬𝑻` や `𝓘𝓷𝓽𝓮𝓻𝓷𝓮𝓽` のような Unicode 上の装飾文字（記号）を用いて表記するお遊びがあると思います。俗に「フォント変換」[^1]などと呼ばれているアレです。
ブラウザからであれば[yaytext](https://yaytext.com/)などで簡単に"変換"できるのですが、Discordのbotに組み込むにあたって（自分にとって）望ましい挙動をするライブラリが見当たらなかったので、サクッと作ってnpmに公開しました。

ドキュメントは **https://www.npmjs.com/package/unicode-text-decorator** をご覧ください。

[^1]: Unicode上に存在する別の文字にreplaceしているだけであり、フォント（書体、タイプフェイス）に対する操作ではないため、技術的にはこの呼称は不正確です。

## 使い方

**Note: 「背景」の節で言及しているアクセシビリティ上の問題についてもご一読ください。**

非常にシンプルです。

```sh
npm i unicode-text-decorator
```

### サンプルコード

```js
const utd = require('unicode-text-decorator');

let wildScreen = utd.decorate('wi(l)d-screen', 'bold_italic_serif');
let baroque = utd.decorate('baroque', 'bold_sans_serif');

console.log(wildScreen + ' ' + baroque);

let nanaDaiba = utd.decorate('Nana Daiba / 大場なな (CV: Moeka Koizumi / 小泉萌香)', 'bold_script');
console.log(nanaDaiba);
```

### 上記コードの出力
```
𝒘𝒊(𝒍)𝒅-𝒔𝒄𝒓𝒆𝒆𝒏 𝗯𝗮𝗿𝗼𝗾𝘂𝗲
𝓝𝓪𝓷𝓪 𝓓𝓪𝓲𝓫𝓪 / 大場なな (𝓒𝓥: 𝓜𝓸𝓮𝓴𝓪 𝓚𝓸𝓲𝔃𝓾𝓶𝓲 / 小泉萌香)
```

記号や非ASCII文字列は無視されます。

## 背景

そもそもこれらの装飾文字は何のためにあるのでしょうか。
𝐓𝐰𝐢𝐭𝐭𝐞𝐫などの𝕀𝕟𝕥𝕖𝕣𝕟𝕖𝕥をより𝒆𝒙𝒄𝒊𝒕𝒊𝒏𝒈にするため……ではありません。これらは**Mathematical Alphanumeric Symbols**として[数式中の文字を表現する](https://ja.wikipedia.org/wiki/%E6%95%B0%E5%AD%A6%E7%94%A8%E8%8B%B1%E6%95%B0%E5%AD%97%E8%A8%98%E5%8F%B7)ために収録されています。

たとえば `𝐀` (U+1D400)は **MATHEMATICAL BOLD CAPITAL A** という名称のグリフです。高校数学でもお馴染みの `ℝ` (U+211D, DOUBLE-STRUCK CAPITAL R) なども収録されています。また、`𝕬`など一般的には馴染みがない[フラクトゥール](https://ja.wikipedia.org/wiki/%E3%83%95%E3%83%A9%E3%82%AF%E3%83%88%E3%82%A5%E3%83%BC%E3%83%AB)文字も収録されています。これらは位相などの分野における数式で使用されるものです。

### [注意点] アクセシビリティ上の問題

これらはあくまで数式を表現するための「記号」であり、ASCII文字列の代替表記ではありません。本質的には**別の文字**です。こんなライブラリを作っておいて言うのも説得力がないのですが、アクセシビリティや検索性を悪化させる可能性があります。

Google検索・Google翻訳などは以下の画像のように通常のASCII文字に置換する処理が走っているので気付きにくいのですが、それ以外の場面では検索やスクリーンリーダなどでの挙動に支障が出ると考えられます。（画像: 2022-05-15時点での検索結果）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185296/95fe753b-a450-cb92-86fe-638a7042325f.png)

たとえば、DuckDuckGoでは検索結果が変わります。（画像: 2022-05-15時点での検索結果）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185296/3da22855-d3cb-4c30-299e-b5c1f180861d.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185296/14c07240-ee70-62c0-b214-40760df89279.png)

本来の用途を離れた欧文の表記に関しては、あくまでお遊びの用途に留めるのが無難でしょう。本ライブラリは上記の注意事項を踏まえた上で利用されることを強く推奨します。

### [注意点] Reservedなコードポイントの存在（欠番）

これらの文字の大半はUnicode 3.1で収録されました。しかしながら、それ以前に使用頻度の高い一部の文字（ℂやℝなど）は[Letterlike Symbols](http://www.unicode.org/charts/PDF/U2100.pdf)に収録されています。それらとの重複を防ぐため、Mathematical Alphanumeric Symbolsではreserved（予約）され、欠番となっている文字があります。そのため、コードポイントの数値を計算するのみではこれらの欠落した文字列への対応ができません。実際、この欠落を考慮していないため文字化けを引き起こしているライブラリも存在します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185296/d7019197-55a8-3e6b-796c-f10df2333dd9.png)
画像：[Mathematical Alphanumeric Symbols](http://www.unicode.org/charts/PDF/U1D400.pdf) より

## 機能

以上の背景を踏まえて、以下の要件を設定しました。

- 複数の文字種別に対応する
- reservedなコードポイントにも対応し、文字化けを発生させない
- 小文字が存在しない場合のfallback

### 小文字が存在しない場合のfallback

数学記号ではないのですが、UnicodeにはOther Symbolsとして 🅐🅑🅒🅓🅔🅕🅖🅗 のような丸囲みのアルファベット記号も存在しています。本ライブラリではこれらにも対応しています。

ただ、一部の種別では大文字しか記号が存在していないものがあります。これらの変換を行った時に

```
Tokyo Tower → 🅣okyo 🅣ower
```
となるのは不本意であり、大文字でも良いので
```
Tokyo Tower → 🅣🅞🅚🅨🅞 🅣🅞🅦🅔🅡
```
となるべき場合があります。

このライブラリでは `{ fallback: true }` オプションを渡すことにより、上記の動作を実現できます。

```js
let tokyoTower1 = utd.decorate('Tokyo Tower', 'black_circle'); // デフォルトは false
let tokyoTower2 = utd.decorate('Tokyo Tower', 'black_circle', { fallback: true });
let tokyoTower3 = utd.decorate('Torre de Tóquio','black_circle', { fallback: true });

console.log(tokyoTower1);
console.log(tokyoTower2);
console.log(tokyoTower3);
```

```
🅣okyo 🅣ower
🅣🅞🅚🅨🅞 🅣🅞🅦🅔🅡
🅣🅞🅡🅡🅔 🅓🅔 🅣ó🅠🅤🅘🅞
```
なお。ASCII以外の文字はfallbackモードでも無視されます。