Unicodeの点字でお絵かき

```rb:5000000000000000yen.js
str = `

⠀⠀⠀⠀⠀⠀⠀⣤⣤⣤⣤⣤⣤⡄⠀⠀⣀⣤⣤⣤⣤⡄⠀⠀⠀⣀⣤⣤⣤⣤⡀⠀⠀⠀⣀⣤⣤⣤⣤⡀⠀⠀⣀⡀⠀⣠⣶⠆⣴⡖⠀⣀⡀⠀⣀⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⡄
⠀⠀⠀⠀⠀⣀⣼⡿⠉⠉⠉⠉⠉⠀⣠⣾⡿⠋⠉⠙⣿⡿⠀⣠⣾⠟⠋⠉⢹⣿⡗⠀⣠⣾⠟⠋⠉⢹⣿⡇⠀⠀⣿⣷⣠⣿⠃⣼⣟⣤⡾⠟⠁⣀⣾⡟⠉⠉⣹⡿⠉⠉⢉⣿⠏⠀
⠀⠀⠀⠀⣠⣾⣿⣶⣶⣶⣦⠀⠀⣴⣿⠋⠀⠀⠀⣸⣿⢇⣼⡿⠃⠀⠀⠀⣼⣿⢃⣾⡿⠁⠀⠀⠀⣾⣿⠁⠀⠀⠛⣩⣿⢃⣾⣟⣝⠋⠀⠀⣀⣾⣟⣀⣀⣴⣿⣁⣀⣠⣿⠏⠀⠀
⠀⠀⠀⠀⠛⠋⠁⠀⠀⣿⡿⠀⣼⣿⠃⠀⠀⣀⣼⣿⠋⣾⣿⠁⠀⠀⣀⣼⡿⢃⣾⡿⠁⠀⠀⣀⣾⡿⠁⣴⣶⠾⣿⡿⢃⣾⡟⠙⣿⣷⠄⣀⣾⠟⠛⠛⠛⠛⠛⠛⢻⣿⠏⠀⠀⠀
⠀⠀⣰⣶⣆⣀⣀⣤⣾⠟⠁⣰⣿⣯⣀⣀⣤⣾⠟⠁⣸⣿⣧⣀⣀⣴⣿⠟⠁⣸⣿⣇⣀⣀⣴⣿⠟⠀⡈⣁⣤⣾⠟⣀⣾⠏⠀⣀⣾⠄⣠⣾⠏⠀⠀⠀⠀⠀⠀⣠⣿⠏⠀⠀⠀⠀
⠀⠀⡈⠛⠿⠿⠛⠋⠁⠀⠀⠀⠛⠿⠿⠛⠋⠁⠀⠀⡈⠛⠿⠟⠛⠋⠀⠀⠀⡈⠛⠿⠟⠛⠉⠀⠀⣶⡿⠟⠋⠀⠀⠾⢿⣷⠾⠿⠋⣠⣿⠏⠀⠀⠀⠀⡸⠿⠶⠿⠋⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣴⣤⢤⣄⠀⣠⣶⠦⠀⠀⠀⠀⠀⡰⣦⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣤⣤⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⡴⢟⣯⣄⣸⣿⣷⠿⠥⣴⣾⠄⠀⠀⠀⣴⡿⠋⠀⠀⠀⠀⠀⠀⠀⣀⣿⡆⠀⠀⠀⠀⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⣼⡿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢉⣴⠿⠛⣷⡴⢋⣿⠟⠞⠋⠀⠀⠀⣀⣼⠟⠀⠀⠀⠀⠀⠀⠀⠀⣀⣾⠟⠀⡀⠀⠀⠀⣈⣿⡀⠀⠀⠀⠀⠀⠀⣰⡟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⢶⣿⠓⣶⣾⠛⣡⣿⡟⠀⠀⠀⠀⠀⣀⣾⠋⠀⠀⠀⠀⠀⡀⠀⠀⠀⣾⠏⣠⠞⠀⠀⣀⣠⣼⡿⠀⠀⠀⠀⠀⠀⣠⠏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣿⠃⣰⡿⣁⣾⠟⣰⣷⡀⠀⠀⠀⠀⣾⡇⠀⣀⣀⣤⡶⠋⠀⠀⠀⡸⣿⣿⠁⠀⠀⠀⠀⠙⠉⠀⠀⠀⠀⠀⣀⣤⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡰⠿⠋⢉⡿⠟⠋⠀⠀⣸⠟⠋⠀⠀⠀⠀⠻⠿⠿⠛⠋⠁⠀⠀⠀⠀⠀⠀⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡘⠛⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
`
console.log(str)
```

[点文字ジェネレータ](http://jsrun.it/zakuroishikuro/MNbhM) ([jsdo.it](http://jsdo.it/zakuroishikuro/MNbhM))

JavaScript(ES2015)で点字を扱う方法についてのメモ。
点字は見た目からして2進数と親和性が高い。
ビット演算を学ぶのにいいかも？

Canvasについては説明なし。
点字の読み方も説明なし。

# 点字の種類

「6点点字(2×3)」と「8点点字(2×4)」の2種類がある。

## 6点点字
Unicodeの`U+2800`〜`U+283F`の64文字。
サイコロの6の目のような2×3の点が並ぶ。
点字の各点にはそれぞれ左上から順番に番号が振られている。

①④
②⑤
③⑥

Unicodeの点字はその番号をn桁目とする2進数の順番に並んでいる。
①③⑤に点を打つなら`010101`で21番目。
Unicodeの文字の名称も`BRAILLE PATTERN DOTS-135`等そのまま。

```js:①③⑤と②④⑥に点を打つ点字を取得
var dots135 = 0b010101 + 0x2800; //0bXXは2進数リテラル
String.fromCodePoint(dots135);
//=> ⠕

var dots246 = 0b101010 + 0x2800;
String.fromCodePoint(dots246);
//=> ⠪
```

全て取得するとこう。

```js
var all = "";
for(var i = 0; i <= 0x3F; i++)
  all += String.fromCodePoint(i + 0x2800);
```

```text
⠀⠁⠂⠃⠄⠅⠆⠇⠈⠉⠊⠋⠌⠍⠎⠏⠐⠑⠒⠓⠔⠕⠖⠗⠘⠙⠚⠛⠜⠝⠞⠟⠠⠡⠢⠣⠤⠥⠦⠧⠨⠩⠪⠫⠬⠭⠮⠯⠰⠱⠲⠳⠴⠵⠶⠷⠸⠹⠺⠻⠼⠽⠾⠿
```

## 8点点字
Unicodeの`U+2800`〜`U+28FF`の256文字。

世界で使われている点字はほぼ全て6点点字。
8点点字は漢字等にしか使われない。[Wikipedia: 8-dot braille scripts](https://en.wikipedia.org/wiki/Category:8-dot_braille_scripts)
この記事ではもちろん8点点字を使う。

点の番号は6点点字の下に点⑦⑧が後付けされる形。

①④
②⑤
③⑥
**⑦⑧**

このままでは2進数そのままの順番で扱えず都合が悪い。⑦の位置に④が来ないと困る。
2進数そのままの順番で点字を得られる関数を作っておく。

```js:2進数から点字へ
function numToBrailleLetter(n){
  var flags = 0;
  flags += (n & 0b00001000) << 3; //⑦を左シフト
  flags += (n & 0b01110000) >> 1; //④⑤⑥を右シフト
  flags += (n & 0b10000111);      //①②③⑧はそのまま
  return String.fromCodePoint(flags + 0x2800);
}

// 使用例:
var n = 0b11110000;//右1列の点
console.log(numToBrailleLetter(n));
//=> ⢸
```

ついでに逆も作る。使う予定は無し。

```js:点字から2進数へ
function brailleLetterToNum(c){
  var u = c.codePointAt(0);
  
  var flags = 0;
  flags += (u & 0b01000000) >> 3;
  flags += (u & 0b00111000) << 1;
  flags += (u & 0b10000111);
  return flags;
}

// 使用例:
var n = brailleLetterToNum("⢸");
console.log(n.toString(2));
//=> 11110000
```

全て取得するとこんな感じ。

```js
var all = "";
for (var i = 0; i <= 0xFF; i++)
  all += numToBrailleLetter(i);
```

```text
⠀⠁⠂⠃⠄⠅⠆⠇⡀⡁⡂⡃⡄⡅⡆⡇⠈⠉⠊⠋⠌⠍⠎⠏⡈⡉⡊⡋⡌⡍⡎⡏
⠐⠑⠒⠓⠔⠕⠖⠗⡐⡑⡒⡓⡔⡕⡖⡗⠘⠙⠚⠛⠜⠝⠞⠟⡘⡙⡚⡛⡜⡝⡞⡟
⠠⠡⠢⠣⠤⠥⠦⠧⡠⡡⡢⡣⡤⡥⡦⡧⠨⠩⠪⠫⠬⠭⠮⠯⡨⡩⡪⡫⡬⡭⡮⡯
⠰⠱⠲⠳⠴⠵⠶⠷⡰⡱⡲⡳⡴⡵⡶⡷⠸⠹⠺⠻⠼⠽⠾⠿⡸⡹⡺⡻⡼⡽⡾⡿
⢀⢁⢂⢃⢄⢅⢆⢇⣀⣁⣂⣃⣄⣅⣆⣇⢈⢉⢊⢋⢌⢍⢎⢏⣈⣉⣊⣋⣌⣍⣎⣏
⢐⢑⢒⢓⢔⢕⢖⢗⣐⣑⣒⣓⣔⣕⣖⣗⢘⢙⢚⢛⢜⢝⢞⢟⣘⣙⣚⣛⣜⣝⣞⣟
⢠⢡⢢⢣⢤⢥⢦⢧⣠⣡⣢⣣⣤⣥⣦⣧⢨⢩⢪⢫⢬⢭⢮⢯⣨⣩⣪⣫⣬⣭⣮⣯
⢰⢱⢲⢳⢴⢵⢶⢷⣰⣱⣲⣳⣴⣵⣶⣷⢸⢹⢺⢻⢼⢽⢾⢿⣸⣹⣺⣻⣼⣽⣾⣿
```

↑これそのままソースに書き込んで使うなら上の変換関数は不要

# Booleanの配列から点字にする

```js
var ary = [
  [true, false],
  [false, true],
  [true, false],
  [false, true]
];
```

上の配列を`⢕`に変換したい。
左上から順番に$2^n$をかけて足せばOK。

```js
var flags = 0;
flags += ary[0][0] * 0b00000001; // 0x01 (1)
flags += ary[1][0] * 0b00000010; // 0x02 (2)
flags += ary[2][0] * 0b00000100; // 0x04 (4)
flags += ary[3][0] * 0b00001000; // 0x08 (8)
flags += ary[0][1] * 0b00010000; // 0x10 (16)
flags += ary[1][1] * 0b00100000; // 0x20 (32)
flags += ary[2][1] * 0b01000000; // 0x40 (64)
flags += ary[3][1] * 0b10000000; // 0x80 (128)

var letter = numToBrailleLetter(flags);
//=> ⢕
```

連番にするために論理和の代入とビットシフトに書き換えて..

```js
var flags = 0;
flags |= ary[0][0] << 0;
flags |= ary[1][0] << 1;
flags |= ary[2][0] << 2;
flags |= ary[3][0] << 3;
flags |= ary[0][1] << 4;
flags |= ary[1][1] << 5;
flags |= ary[2][1] << 6;
flags |= ary[3][1] << 7;

var letter = numToBrailleLetter(flags);
//=> ⢕
```

ループにすればすっきり。

```js
var flags = 0;
  
for (var r = 0; r < 4; r++)
  for (var c = 0; c < 2; c++)
    flags |= ary[r][c] << r + c * 4;

var letter = numToBrailleLetter(flags);
//=> ⢕
```

## 関数

上のを元にして作った、配列が何行*何列でも点字に変換する関数。


```js:Booleanの配列から点字の文字列にする関数
function boolAryToBrailleLetters(ary){
  var flagsAry = [];
  
  for (var r = 0; r < ary.length; r++){
    if (r % 4 == 0) flagsAry.push([]);
    for (var c = 0; c < ary[r].length; c++){
      flagsAry[r / 4 | 0][c / 2 | 0] |= ary[r][c] << r % 4 + c % 2 * 4;)
    }
  }
  
  return flagsAry.map(a=>a.map(n=>numToBrailleLetter(n)).join("")).join("\r\n");
}
```

```js:使用例
var ary = [
  [true, true, true],
  [true, true, true],
  [true, true, true],
  [true, true, true],
  [true, true, true]
];

console.log(boolAryToBrailleLetters(ary));
// ⣿⡇
// ⠉⠁

var ary = [[true]];

console.log(boolAryToBrailleLetters(ary));
// ⠁

```

あとはCanvasとかから画像のRGBデータを受け取って、RGBの平均とって白黒(true/false)の2値化してこの関数に渡すだけ。

動作例:http://jsdo.it/zakuroishikuro/MNbhM

おわり。

# ズレ問題

点字は全て同じ幅なので、他の文字で作ったAAと違ってズレないはず。
そう思っていた時期が私にもありました。

- Mac・iPhoneでは完璧
- Windowsではズレる
- Androidではフォントが間違ってるので乱れる

結局フォントによるんだけど、点字を収録しているフォントなんてそうないし、フォントごとの個性を出しちゃいけない文字だと思うし、個人差無しのデフォのままで考えていいと思う。

## Mac・iPhone
表示フォント: 「Apple Braille」 (Apple Symbolsにも一応入ってる)
Macには点字専用フォントが用意されている。
全くズレずに完璧に表示される。
iPhoneのフォント事情は知らないけどズレなかったのでMacと同じなのでは。

## Windows
表示フォント: 「Segoe UI Symbol」
点字用の空白`U+2800`の文字幅が他の点字と違うのでズレる。無意味。
本来の実用上は問題ないのかもしれないけど、なぜ同じ幅にしてくれなかった...。

対策：`U+2800`をなるべく目立たなそうな`⡀`で置換

## Android
表示フォント：「Noto Sans Symbols」 (コメントで教えていただきました)

フォント内の一部の点字が間違っている。[GitHubにIssueあり](https://github.com/googlei18n/noto-fonts/issues/932)
右側にしか点が無いものが全て左寄りになってる。
つまり`⠈⠐⠘⠠⠨⠰⠸⢀⢈⢐⢘⢠⢨⢰⢸`が`⠁⠂⠃⠄⠅⠆⠇⡀⡁⡂⡃⡄⡅⡆⡇`と同じになってる。

対策：左下に点をつけて置換
`⠈⠐⠘⠠⠨⠰⠸⢀⢈⢐⢘⢠⢨⢰⢸`→`⡀⡈⡐⡘⡠⡨⡰⡸⣀⣈⣐⣘⣠⣨⣰⣸`

## Linux
分かりません。教えてください。

# 参考
[Wikipedia - Braille Patterns](https://en.wikipedia.org/wiki/Braille_Patterns)

点字で画像を表現するライブラリ (この記事書いたあとに見つけた)
[mapscii](https://github.com/rastapasta/mapscii)
[drawille](https://github.com/asciimoo/drawille)

ちなみに[@nullkal氏のトゥート](https://mstdn.jp/users/nullkal/updates/15867835)で思いついた