日本語を始めとして世界中の文字では「ほぼ同じ字だが、字の一部がほんのちょっとだけ違うバリーエーション」が結構あります。それぞれ別のコードに割り当てられることもありますが、それをやっちゃうとキリがないので一つのコードポイントにまとめられてしまっている場合もあります。でも、それだと(おそらく戸籍とかの表記で)困るため、接尾に区別するためのコードを追加することになってます(そういうことをやるから実装が複雑になるんだよ)。その区別するためのコード、それが異体字セレクタというものです。詳しくはこちらを:
- 【Golang】Unicode上複数コードからなる文字をruneで扱う場合の挙動 - Ren's blog
- 日本語は1文字何バイト? - Sanwa Systems Tech Blog
- 異体字セレクタ - Wikipedia
邊(U+908A)、邊󠄄(U+908A U+E0104:本当はシンニョウ部分の点が一つ)を例に検証してみました。
Windows で検証したところ:
- 入力
- Web などで {U+908A U+E0104} をコピーした時
- クリップボードには {U+908A U+E0104}とセレクタのコードも含めて記憶される
- コンソールの右クリックによるペーストでは U+E0104 はパスされて U+908A だけになってしまう
- Web などで {U+908A U+E0104} をコピーした時
- 出力
- コマンドプロンプトでは、セレクタコードは <?> マークになってしまう
- WindowsTerminal では、セレクタコードのついた文字はフォントは一応変わり(部首の一部が微妙に変わってるだけなのでわかりにくい){U+908A U+E0104} で3セル分になるように表示が少し変わる
package main
import (
"fmt"
)
func main(){
fmt.Printf("[%c][%c%c]\n",0x908A,0x908A,0xE0104)
}
普通に表示できるんだから、一行入力の入力も楽勝でしょう。合字も出来たんだから同じように出来るやろ?そう思っていた時期が私にもありました。
ふぁっ?(変な声出た)。何かおかしい。どうも、カーソル位置の認識がずれているようです。
package main
import (
"fmt"
)
func main() {
fmt.Printf("[%c][%c%c]\n", 0x908A, 0x908A, 0xE0104)
fmt.Printf("[%c][%c%c", 0x908A, 0x908A, 0xE0104)
fmt.Printf("]\n")
}
どうも、異体字セレクタを出力した後のカーソル位置の認識がおかしくなっているようです。出力を止める前は3文字と認識しているのに、止めた後に4文字に変わってしまってます。これは困ります。というのも、一行入力での文字列出力は Windows の API にテキストを流すまで、io.Writer・bufio.Writer・go-colorable など多数の層を挟んでいるため、都合よくここで出力を止めるなどということはできないのです。うーむ。
半日ほど悩んだ結果、異体字は
fmt.Fprintf(w, " \x1B7\b\b\b\b%s\x1B8", string(s))
と出力することで、回避しました。
- 4文字分位置まで、ますスペース文字で移動
- カーソル位置を
ESC 7
で記憶 - 元の位置までバックスペースで復帰
- 異体字を出力する
-
ESC 8
で 2. で記憶した4文字分の位置まで移動
つまり、常に4文字分のサイズであるかのようにカーソル位置を調整するようにしたのです。やったね!
なお、この試行錯誤の過程については zenn のスクラップ「Unicodeとの異体字バトルがはじまったぜ」で記されているので、よろしければご覧ください。